mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 07:11:29 +08:00
Merge branch 'next' into support-props-exclude
This commit is contained in:
commit
fee187460e
@ -7,7 +7,7 @@
|
||||
- [React Native Async Storage](#react-native-async-storage)
|
||||
- [Deprecate displayName parameter](#deprecate-displayname-parameter)
|
||||
- [Unified docs preset](#unified-docs-preset)
|
||||
- [Simplified hierarchy separators](#simplified-heirarchy-separators)
|
||||
- [Simplified hierarchy separators](#simplified-hierarchy-separators)
|
||||
- [From version 5.1.x to 5.2.x](#from-version-51x-to-52x)
|
||||
- [Source-loader](#source-loader)
|
||||
- [Default viewports](#default-viewports)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import React, { FunctionComponent, useContext } from 'react';
|
||||
import { Description, DescriptionProps as PureDescriptionProps } from '@storybook/components';
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { Component, CURRENT_SELECTION } from './shared';
|
||||
import { Component, CURRENT_SELECTION, DescriptionSlot } from './shared';
|
||||
import { str } from '../lib/docgen/utils';
|
||||
|
||||
export enum DescriptionType {
|
||||
@ -16,9 +16,11 @@ type Notes = string | any;
|
||||
type Info = string | any;
|
||||
|
||||
interface DescriptionProps {
|
||||
slot?: DescriptionSlot;
|
||||
of?: '.' | Component;
|
||||
type?: DescriptionType;
|
||||
markdown?: string;
|
||||
children?: string;
|
||||
}
|
||||
|
||||
const getNotes = (notes?: Notes) =>
|
||||
@ -29,11 +31,11 @@ const getInfo = (info?: Info) => info && (typeof info === 'string' ? info : str(
|
||||
const noDescription = (component?: Component): string | null => null;
|
||||
|
||||
export const getDescriptionProps = (
|
||||
{ of, type, markdown }: DescriptionProps,
|
||||
{ of, type, markdown, children }: DescriptionProps,
|
||||
{ parameters }: DocsContextProps
|
||||
): PureDescriptionProps => {
|
||||
if (markdown) {
|
||||
return { markdown };
|
||||
if (children || markdown) {
|
||||
return { markdown: children || markdown };
|
||||
}
|
||||
const { component, notes, info, docs } = parameters;
|
||||
const { extractComponentDescription = noDescription } = docs || {};
|
||||
@ -59,13 +61,19 @@ ${extractComponentDescription(target) || ''}
|
||||
}
|
||||
};
|
||||
|
||||
const DescriptionContainer: FunctionComponent<DescriptionProps> = props => (
|
||||
<DocsContext.Consumer>
|
||||
{context => {
|
||||
const { markdown } = getDescriptionProps(props, context);
|
||||
return markdown && <Description markdown={markdown} />;
|
||||
}}
|
||||
</DocsContext.Consumer>
|
||||
);
|
||||
const DescriptionContainer: FunctionComponent<DescriptionProps> = props => {
|
||||
const context = useContext(DocsContext);
|
||||
const { slot } = props;
|
||||
let { markdown } = getDescriptionProps(props, context);
|
||||
if (slot) {
|
||||
markdown = slot(markdown, context);
|
||||
}
|
||||
return markdown ? <Description markdown={markdown} /> : null;
|
||||
};
|
||||
|
||||
// since we are in the docs blocks, assume default description if for primary component story
|
||||
DescriptionContainer.defaultProps = {
|
||||
of: '.',
|
||||
};
|
||||
|
||||
export { DescriptionContainer as Description };
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defaultTitleSlot } from './DocsPage';
|
||||
import { defaultTitleSlot } from './Title';
|
||||
|
||||
describe('defaultTitleSlot', () => {
|
||||
it('showRoots', () => {
|
||||
|
@ -1,153 +1,26 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { parseKind } from '@storybook/router';
|
||||
import { DocsPage as PureDocsPage, PropsTable, PropsTableProps } from '@storybook/components';
|
||||
import { H2, H3 } from '@storybook/components/html';
|
||||
import { DocsContext } from './DocsContext';
|
||||
import { DocsPageProps } from './shared';
|
||||
import { Title } from './Title';
|
||||
import { Subtitle } from './Subtitle';
|
||||
import { Description } from './Description';
|
||||
import { Story } from './Story';
|
||||
import { Preview } from './Preview';
|
||||
import { Anchor } from './Anchor';
|
||||
import { getPropsTableProps } from './Props';
|
||||
|
||||
export interface SlotContext {
|
||||
id?: string;
|
||||
selectedKind?: string;
|
||||
selectedStory?: string;
|
||||
parameters?: any;
|
||||
storyStore?: any;
|
||||
}
|
||||
|
||||
export type StringSlot = (context: SlotContext) => string | void;
|
||||
export type PropsSlot = (context: SlotContext) => PropsTableProps | void;
|
||||
export type StorySlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps | void;
|
||||
export type StoriesSlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps[] | void;
|
||||
|
||||
export interface DocsPageProps {
|
||||
titleSlot: StringSlot;
|
||||
subtitleSlot: StringSlot;
|
||||
descriptionSlot: StringSlot;
|
||||
primarySlot: StorySlot;
|
||||
propsSlot: PropsSlot;
|
||||
storiesSlot: StoriesSlot;
|
||||
}
|
||||
|
||||
interface DocsStoryProps {
|
||||
id: string;
|
||||
name: string;
|
||||
expanded?: boolean;
|
||||
withToolbar?: boolean;
|
||||
parameters?: any;
|
||||
}
|
||||
|
||||
interface StoryData {
|
||||
id: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
parameters?: any;
|
||||
}
|
||||
|
||||
export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => {
|
||||
const {
|
||||
showRoots,
|
||||
hierarchyRootSeparator: rootSeparator,
|
||||
hierarchySeparator: groupSeparator,
|
||||
} = (parameters && parameters.options) || {
|
||||
showRoots: undefined,
|
||||
hierarchyRootSeparator: '|',
|
||||
hierarchySeparator: /\/|\./,
|
||||
};
|
||||
|
||||
let groups;
|
||||
if (typeof showRoots !== 'undefined') {
|
||||
groups = selectedKind.split('/');
|
||||
} else {
|
||||
// This covers off all the remaining cases:
|
||||
// - If the separators were set above, we should use them
|
||||
// - If they weren't set, we should only should use the old defaults if the kind contains '.' or '|',
|
||||
// which for this particular splitting is the only case in which it actually matters.
|
||||
({ groups } = parseKind(selectedKind, { rootSeparator, groupSeparator }));
|
||||
}
|
||||
|
||||
return (groups && groups[groups.length - 1]) || selectedKind;
|
||||
};
|
||||
|
||||
const defaultSubtitleSlot: StringSlot = ({ parameters }) =>
|
||||
parameters && parameters.componentSubtitle;
|
||||
|
||||
const defaultPropsSlot: PropsSlot = context => getPropsTableProps({ of: '.' }, context);
|
||||
|
||||
const defaultDescriptionSlot: StringSlot = ({ parameters }) => {
|
||||
const { component, docs } = parameters;
|
||||
if (!component) {
|
||||
return null;
|
||||
}
|
||||
const { extractComponentDescription } = docs || {};
|
||||
return extractComponentDescription && extractComponentDescription(component, parameters);
|
||||
};
|
||||
|
||||
const defaultPrimarySlot: StorySlot = stories => stories && stories[0];
|
||||
const defaultStoriesSlot: StoriesSlot = stories => {
|
||||
if (stories && stories.length > 1) {
|
||||
const [first, ...rest] = stories;
|
||||
return rest;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const StoriesHeading = H2;
|
||||
const StoryHeading = H3;
|
||||
|
||||
const DocsStory: FunctionComponent<DocsStoryProps> = ({
|
||||
id,
|
||||
name,
|
||||
expanded = true,
|
||||
withToolbar = false,
|
||||
parameters,
|
||||
}) => (
|
||||
<Anchor storyId={id}>
|
||||
{expanded && <StoryHeading>{name}</StoryHeading>}
|
||||
{expanded && parameters && parameters.docs && parameters.docs.storyDescription && (
|
||||
<Description markdown={parameters.docs.storyDescription} />
|
||||
)}
|
||||
<Preview withToolbar={withToolbar}>
|
||||
<Story id={id} />
|
||||
</Preview>
|
||||
</Anchor>
|
||||
);
|
||||
import { Primary } from './Primary';
|
||||
import { Props } from './Props';
|
||||
import { Stories } from './Stories';
|
||||
|
||||
export const DocsPage: FunctionComponent<DocsPageProps> = ({
|
||||
titleSlot = defaultTitleSlot,
|
||||
subtitleSlot = defaultSubtitleSlot,
|
||||
descriptionSlot = defaultDescriptionSlot,
|
||||
primarySlot = defaultPrimarySlot,
|
||||
propsSlot = defaultPropsSlot,
|
||||
storiesSlot = defaultStoriesSlot,
|
||||
titleSlot,
|
||||
subtitleSlot,
|
||||
descriptionSlot,
|
||||
primarySlot,
|
||||
propsSlot,
|
||||
storiesSlot,
|
||||
}) => (
|
||||
<DocsContext.Consumer>
|
||||
{context => {
|
||||
const title = titleSlot(context) || '';
|
||||
const subtitle = subtitleSlot(context) || '';
|
||||
const description = descriptionSlot(context) || '';
|
||||
const propsTableProps = propsSlot(context);
|
||||
|
||||
const { selectedKind, storyStore } = context;
|
||||
const componentStories = storyStore
|
||||
.getStoriesForKind(selectedKind)
|
||||
.filter((s: any) => !(s.parameters && s.parameters.docs && s.parameters.docs.disable));
|
||||
const primary = primarySlot(componentStories, context);
|
||||
const stories = storiesSlot(componentStories, context);
|
||||
|
||||
return (
|
||||
<PureDocsPage title={title} subtitle={subtitle}>
|
||||
<Description markdown={description} />
|
||||
{primary && <DocsStory key={primary.id} {...primary} expanded={false} withToolbar />}
|
||||
{propsTableProps && <PropsTable {...propsTableProps} />}
|
||||
{stories && stories.length > 0 && <StoriesHeading>Stories</StoriesHeading>}
|
||||
{stories &&
|
||||
stories.map(story => story && <DocsStory key={story.id} {...story} expanded />)}
|
||||
</PureDocsPage>
|
||||
);
|
||||
}}
|
||||
</DocsContext.Consumer>
|
||||
<>
|
||||
<Title slot={titleSlot} />
|
||||
<Subtitle slot={subtitleSlot} />
|
||||
<Description slot={descriptionSlot} />
|
||||
<Primary slot={primarySlot} />
|
||||
<Props slot={propsSlot} />
|
||||
<Stories slot={storiesSlot} />
|
||||
</>
|
||||
);
|
||||
|
25
addons/docs/src/blocks/DocsStory.tsx
Normal file
25
addons/docs/src/blocks/DocsStory.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { Subheading } from './Subheading';
|
||||
import { DocsStoryProps } from './shared';
|
||||
import { Anchor } from './Anchor';
|
||||
import { Description } from './Description';
|
||||
import { Story } from './Story';
|
||||
import { Preview } from './Preview';
|
||||
|
||||
export const DocsStory: FunctionComponent<DocsStoryProps> = ({
|
||||
id,
|
||||
name,
|
||||
expanded = true,
|
||||
withToolbar = false,
|
||||
parameters,
|
||||
}) => (
|
||||
<Anchor storyId={id}>
|
||||
{expanded && <Subheading>{name}</Subheading>}
|
||||
{expanded && parameters && parameters.docs && parameters.docs.storyDescription && (
|
||||
<Description markdown={parameters.docs.storyDescription} />
|
||||
)}
|
||||
<Preview withToolbar={withToolbar}>
|
||||
<Story id={id} />
|
||||
</Preview>
|
||||
</Anchor>
|
||||
);
|
7
addons/docs/src/blocks/Heading.tsx
Normal file
7
addons/docs/src/blocks/Heading.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { H2 } from '@storybook/components/html';
|
||||
|
||||
interface HeadingProps {
|
||||
children: JSX.Element | string;
|
||||
}
|
||||
export const Heading: FunctionComponent<HeadingProps> = ({ children }) => <H2>{children}</H2>;
|
16
addons/docs/src/blocks/Primary.tsx
Normal file
16
addons/docs/src/blocks/Primary.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { useContext, FunctionComponent } from 'react';
|
||||
import { DocsContext } from './DocsContext';
|
||||
import { DocsStory } from './DocsStory';
|
||||
import { getDocsStories } from './utils';
|
||||
import { StorySlot } from './shared';
|
||||
|
||||
interface PrimaryProps {
|
||||
slot?: StorySlot;
|
||||
}
|
||||
|
||||
export const Primary: FunctionComponent<PrimaryProps> = ({ slot }) => {
|
||||
const context = useContext(DocsContext);
|
||||
const componentStories = getDocsStories(context);
|
||||
const story = slot ? slot(componentStories, context) : componentStories && componentStories[0];
|
||||
return story ? <DocsStory {...story} expanded={false} withToolbar /> : null;
|
||||
};
|
@ -1,15 +1,22 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { PropsTable, PropsTableError, PropsTableProps, PropDef } from '@storybook/components';
|
||||
import React, { FunctionComponent, useContext } from 'react';
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
import { PropsTable, PropsTableError, PropsTableProps, TabsState } from '@storybook/components';
|
||||
import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
import { Component, CURRENT_SELECTION } from './shared';
|
||||
import { Component, PropsSlot, CURRENT_SELECTION } from './shared';
|
||||
import { getComponentName } from './utils';
|
||||
|
||||
import { PropsExtractor } from '../lib/docgen/types';
|
||||
import { extractProps as reactExtractProps } from '../frameworks/react/extractProps';
|
||||
import { extractProps as vueExtractProps } from '../frameworks/vue/extractProps';
|
||||
|
||||
interface PropsProps {
|
||||
exclude?: string[];
|
||||
of: '.' | Component;
|
||||
of?: '.' | Component;
|
||||
components?: {
|
||||
[label: string]: Component;
|
||||
};
|
||||
slot?: PropsSlot;
|
||||
}
|
||||
|
||||
// FIXME: remove in SB6.0 & require config
|
||||
@ -24,24 +31,22 @@ const inferPropsExtractor = (framework: string): PropsExtractor | null => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getPropsTableProps = (
|
||||
{ exclude, of }: PropsProps,
|
||||
export const getComponentProps = (
|
||||
component: Component,
|
||||
{ exclude }: PropsProps,
|
||||
{ parameters }: DocsContextProps
|
||||
): PropsTableProps => {
|
||||
if (!component) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const params = parameters || {};
|
||||
const { component, framework = null } = params;
|
||||
|
||||
const target = of === CURRENT_SELECTION ? component : of;
|
||||
if (!target) {
|
||||
throw new Error(PropsTableError.NO_COMPONENT);
|
||||
}
|
||||
const { framework = null } = params;
|
||||
|
||||
const { extractProps = inferPropsExtractor(framework) } = params.docs || {};
|
||||
if (!extractProps) {
|
||||
throw new Error(PropsTableError.PROPS_UNSUPPORTED);
|
||||
}
|
||||
|
||||
let { rows } = extractProps(target);
|
||||
if (!isNil(exclude)) {
|
||||
rows = rows.filter((row: PropDef) => !exclude.includes(row.name));
|
||||
@ -53,13 +58,70 @@ export const getPropsTableProps = (
|
||||
}
|
||||
};
|
||||
|
||||
const PropsContainer: FunctionComponent<PropsProps> = props => (
|
||||
<DocsContext.Consumer>
|
||||
{context => {
|
||||
const propsTableProps = getPropsTableProps(props, context);
|
||||
return <PropsTable {...propsTableProps} />;
|
||||
}}
|
||||
</DocsContext.Consumer>
|
||||
);
|
||||
export const getComponent = (props: PropsProps = {}, context: DocsContextProps): Component => {
|
||||
const { of } = props;
|
||||
const { parameters = {} } = context;
|
||||
const { component } = parameters;
|
||||
|
||||
const target = of === CURRENT_SELECTION ? component : of;
|
||||
if (!target) {
|
||||
if (of === CURRENT_SELECTION) {
|
||||
return null;
|
||||
}
|
||||
throw new Error(PropsTableError.NO_COMPONENT);
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
const PropsContainer: FunctionComponent<PropsProps> = props => {
|
||||
const context = useContext(DocsContext);
|
||||
const { slot, components } = props;
|
||||
const {
|
||||
parameters: { subcomponents },
|
||||
} = context;
|
||||
|
||||
let allComponents = components;
|
||||
if (!allComponents) {
|
||||
const main = getComponent(props, context);
|
||||
const mainLabel = getComponentName(main);
|
||||
const mainProps = slot ? slot(context, main) : getComponentProps(main, props, context);
|
||||
|
||||
if (!subcomponents || typeof subcomponents !== 'object') {
|
||||
return mainProps && <PropsTable {...mainProps} />;
|
||||
}
|
||||
|
||||
allComponents = { [mainLabel]: main, ...subcomponents };
|
||||
}
|
||||
|
||||
const tabs: { label: string; table: PropsTableProps }[] = [];
|
||||
Object.entries(allComponents).forEach(([label, component]) => {
|
||||
tabs.push({
|
||||
label,
|
||||
table: slot ? slot(context, component) : getComponentProps(component, props, context),
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<TabsState>
|
||||
{tabs.map(({ label, table }) => {
|
||||
if (!table) {
|
||||
return null;
|
||||
}
|
||||
const id = `prop_table_div_${label}`;
|
||||
return (
|
||||
<div key={id} id={id} title={label}>
|
||||
{({ active }: { active: boolean }) =>
|
||||
active ? <PropsTable key={`prop_table_${label}`} {...table} /> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</TabsState>
|
||||
);
|
||||
};
|
||||
|
||||
PropsContainer.defaultProps = {
|
||||
of: '.',
|
||||
};
|
||||
|
||||
export { PropsContainer as Props };
|
||||
|
33
addons/docs/src/blocks/Stories.tsx
Normal file
33
addons/docs/src/blocks/Stories.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { useContext, FunctionComponent } from 'react';
|
||||
import { DocsContext } from './DocsContext';
|
||||
import { DocsStory } from './DocsStory';
|
||||
import { Heading } from './Heading';
|
||||
import { getDocsStories } from './utils';
|
||||
import { StoriesSlot, DocsStoryProps } from './shared';
|
||||
|
||||
interface StoriesProps {
|
||||
slot?: StoriesSlot;
|
||||
title?: JSX.Element | string;
|
||||
}
|
||||
|
||||
export const Stories: FunctionComponent<StoriesProps> = ({ slot, title }) => {
|
||||
const context = useContext(DocsContext);
|
||||
const componentStories = getDocsStories(context);
|
||||
|
||||
const stories: DocsStoryProps[] = slot
|
||||
? slot(componentStories, context)
|
||||
: componentStories && componentStories.slice(1);
|
||||
if (!stories) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Heading>{title}</Heading>
|
||||
{stories.map(story => story && <DocsStory key={story.id} {...story} expanded />)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Stories.defaultProps = {
|
||||
title: 'Stories',
|
||||
};
|
7
addons/docs/src/blocks/Subheading.tsx
Normal file
7
addons/docs/src/blocks/Subheading.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { H3 } from '@storybook/components/html';
|
||||
|
||||
interface SubheadingProps {
|
||||
children: JSX.Element | string;
|
||||
}
|
||||
export const Subheading: FunctionComponent<SubheadingProps> = ({ children }) => <H3>{children}</H3>;
|
19
addons/docs/src/blocks/Subtitle.tsx
Normal file
19
addons/docs/src/blocks/Subtitle.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React, { useContext, FunctionComponent } from 'react';
|
||||
import { Subtitle as PureSubtitle } from '@storybook/components';
|
||||
import { DocsContext } from './DocsContext';
|
||||
import { StringSlot } from './shared';
|
||||
|
||||
interface SubtitleProps {
|
||||
slot?: StringSlot;
|
||||
children?: JSX.Element | string;
|
||||
}
|
||||
|
||||
export const Subtitle: FunctionComponent<SubtitleProps> = ({ slot, children }) => {
|
||||
const context = useContext(DocsContext);
|
||||
const { parameters } = context;
|
||||
let text: JSX.Element | string = children;
|
||||
if (!text) {
|
||||
text = slot ? slot(context) : parameters && parameters.componentSubtitle;
|
||||
}
|
||||
return text ? <PureSubtitle className="sbdocs-subtitle">{text}</PureSubtitle> : null;
|
||||
};
|
48
addons/docs/src/blocks/Title.tsx
Normal file
48
addons/docs/src/blocks/Title.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useContext, FunctionComponent } from 'react';
|
||||
import { parseKind } from '@storybook/router';
|
||||
import { Title as PureTitle } from '@storybook/components';
|
||||
import { DocsContext } from './DocsContext';
|
||||
import { StringSlot } from './shared';
|
||||
|
||||
interface TitleProps {
|
||||
slot?: StringSlot;
|
||||
children?: JSX.Element | string;
|
||||
}
|
||||
export const defaultTitleSlot: StringSlot = ({ selectedKind, parameters }) => {
|
||||
const {
|
||||
showRoots,
|
||||
hierarchyRootSeparator: rootSeparator,
|
||||
hierarchySeparator: groupSeparator,
|
||||
} = (parameters && parameters.options) || {
|
||||
showRoots: undefined,
|
||||
hierarchyRootSeparator: '|',
|
||||
hierarchySeparator: /\/|\./,
|
||||
};
|
||||
|
||||
let groups;
|
||||
if (typeof showRoots !== 'undefined') {
|
||||
groups = selectedKind.split('/');
|
||||
} else {
|
||||
// This covers off all the remaining cases:
|
||||
// - If the separators were set above, we should use them
|
||||
// - If they weren't set, we should only should use the old defaults if the kind contains '.' or '|',
|
||||
// which for this particular splitting is the only case in which it actually matters.
|
||||
({ groups } = parseKind(selectedKind, { rootSeparator, groupSeparator }));
|
||||
}
|
||||
|
||||
return (groups && groups[groups.length - 1]) || selectedKind;
|
||||
};
|
||||
|
||||
export const Title: FunctionComponent<TitleProps> = ({ slot, children }) => {
|
||||
const context = useContext(DocsContext);
|
||||
const { selectedKind, parameters } = context;
|
||||
let text: JSX.Element | string = children;
|
||||
if (!text) {
|
||||
if (slot) {
|
||||
text = slot(context);
|
||||
} else {
|
||||
text = defaultTitleSlot({ selectedKind, parameters });
|
||||
}
|
||||
}
|
||||
return text ? <PureTitle className="sbdocs-title">{text}</PureTitle> : null;
|
||||
};
|
@ -5,12 +5,21 @@ export * from './Description';
|
||||
export * from './DocsContext';
|
||||
export * from './DocsPage';
|
||||
export * from './DocsContainer';
|
||||
export * from './DocsStory';
|
||||
export * from './Heading';
|
||||
export * from './Meta';
|
||||
export * from './Preview';
|
||||
export * from './Primary';
|
||||
export * from './Props';
|
||||
export * from './Source';
|
||||
export * from './Stories';
|
||||
export * from './Story';
|
||||
export * from './Subheading';
|
||||
export * from './Subtitle';
|
||||
export * from './Title';
|
||||
export * from './Wrapper';
|
||||
|
||||
export * from './shared';
|
||||
|
||||
// helper function for MDX
|
||||
export const makeStoryFn = (val: any) => (typeof val === 'function' ? val : () => val);
|
||||
|
@ -1,2 +1,41 @@
|
||||
import { PropsTableProps } from '@storybook/components';
|
||||
|
||||
export const CURRENT_SELECTION = '.';
|
||||
|
||||
export type Component = any;
|
||||
|
||||
export interface StoryData {
|
||||
id?: string;
|
||||
kind?: string;
|
||||
name?: string;
|
||||
parameters?: any;
|
||||
}
|
||||
|
||||
export type DocsStoryProps = StoryData & {
|
||||
expanded?: boolean;
|
||||
withToolbar?: boolean;
|
||||
};
|
||||
|
||||
export interface SlotContext {
|
||||
id?: string;
|
||||
selectedKind?: string;
|
||||
selectedStory?: string;
|
||||
parameters?: any;
|
||||
storyStore?: any;
|
||||
}
|
||||
|
||||
export type StringSlot = (context: SlotContext) => string;
|
||||
export type DescriptionSlot = (description: string, context: SlotContext) => string;
|
||||
export type PropsSlot = (context: SlotContext, component: Component) => PropsTableProps;
|
||||
export type StorySlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps;
|
||||
|
||||
export type StoriesSlot = (stories: StoryData[], context: SlotContext) => DocsStoryProps[];
|
||||
|
||||
export interface DocsPageProps {
|
||||
titleSlot?: StringSlot;
|
||||
subtitleSlot?: StringSlot;
|
||||
descriptionSlot?: DescriptionSlot;
|
||||
primarySlot?: StorySlot;
|
||||
propsSlot?: PropsSlot;
|
||||
storiesSlot?: StoriesSlot;
|
||||
}
|
||||
|
34
addons/docs/src/blocks/utils.ts
Normal file
34
addons/docs/src/blocks/utils.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { DocsContextProps } from './DocsContext';
|
||||
import { StoryData, Component } from './shared';
|
||||
|
||||
export const getDocsStories = (context: DocsContextProps): StoryData[] => {
|
||||
const { storyStore, selectedKind } = context;
|
||||
return storyStore
|
||||
.getStoriesForKind(selectedKind)
|
||||
.filter((s: any) => !(s.parameters && s.parameters.docs && s.parameters.docs.disable));
|
||||
};
|
||||
|
||||
const titleCase = (str: string): string =>
|
||||
str
|
||||
.split('-')
|
||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join('');
|
||||
|
||||
export const getComponentName = (component: Component): string => {
|
||||
if (!component) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof component === 'string') {
|
||||
if (component.includes('-')) {
|
||||
return titleCase(component);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
if (component.__docgenInfo && component.__docgenInfo.displayName) {
|
||||
return component.__docgenInfo.displayName;
|
||||
}
|
||||
|
||||
return component.name;
|
||||
};
|
@ -65,7 +65,7 @@ function generateFunc({ inferedType, ast }: InspectionResult): PropDefaultValue
|
||||
}
|
||||
|
||||
// All elements are JSX elements.
|
||||
// JSX elements cannot are not supported by escodegen.
|
||||
// JSX elements are not supported by escodegen.
|
||||
function generateElement(
|
||||
defaultValue: string,
|
||||
inspectionResult: InspectionResult
|
||||
|
@ -47,6 +47,7 @@ function extractPropDef(component: Component): PropDef {
|
||||
}
|
||||
|
||||
describe('enhancePropTypesProp', () => {
|
||||
describe('type', () => {
|
||||
function createTestComponent(docgenInfo: Partial<DocgenInfo>): Component {
|
||||
return createComponent({
|
||||
docgenInfo: {
|
||||
@ -55,7 +56,6 @@ describe('enhancePropTypesProp', () => {
|
||||
});
|
||||
}
|
||||
|
||||
describe('type', () => {
|
||||
describe('custom', () => {
|
||||
describe('when raw value is available', () => {
|
||||
it('should support literal', () => {
|
||||
@ -82,9 +82,7 @@ describe('enhancePropTypesProp', () => {
|
||||
|
||||
const { type } = extractPropDef(component);
|
||||
|
||||
const expectedSummary = `{
|
||||
text: string
|
||||
}`;
|
||||
const expectedSummary = '{ text: string }';
|
||||
|
||||
expect(type.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, ''));
|
||||
expect(type.detail).toBeUndefined();
|
||||
@ -707,10 +705,185 @@ describe('enhancePropTypesProp', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultValue', () => {
|
||||
function createTestComponent(defaultValue: string): Component {
|
||||
return createComponent({
|
||||
docgenInfo: {
|
||||
...createDocgenProp({
|
||||
name: 'prop',
|
||||
type: { name: 'custom' },
|
||||
defaultValue: { value: defaultValue },
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
it('should support short object', () => {
|
||||
const component = createTestComponent("{ foo: 'foo', bar: 'bar' }");
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
const expectedSummary = "{ foo: 'foo', bar: 'bar' }";
|
||||
|
||||
expect(defaultValue.summary.replace(/\s/g, '')).toBe(expectedSummary.replace(/\s/g, ''));
|
||||
expect(defaultValue.detail).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should support long object', () => {
|
||||
const component = createTestComponent("{ foo: 'foo', bar: 'bar', another: 'another' }");
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('object');
|
||||
|
||||
const expectedDetail = `{
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
another: 'another'
|
||||
}`;
|
||||
|
||||
expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
|
||||
});
|
||||
|
||||
it('should support short function', () => {
|
||||
const component = createTestComponent('() => {}');
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('() => {}');
|
||||
expect(defaultValue.detail).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should support long function', () => {
|
||||
const component = createTestComponent(
|
||||
'(foo, bar) => {\n const concat = foo + bar;\n const append = concat + " hey!";\n \n return append;\n}'
|
||||
);
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('func');
|
||||
|
||||
const expectedDetail = `(foo, bar) => {
|
||||
const concat = foo + bar;
|
||||
const append = concat + ' hey!';
|
||||
return append
|
||||
}`;
|
||||
|
||||
expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
|
||||
});
|
||||
|
||||
it('should use the name of function when available and indicate that args are present', () => {
|
||||
const component = createTestComponent('function concat(a, b) {\n return a + b;\n}');
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('concat( ... )');
|
||||
|
||||
const expectedDetail = `function concat(a, b) {
|
||||
return a + b
|
||||
}`;
|
||||
|
||||
expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
|
||||
});
|
||||
|
||||
it('should use the name of function when available', () => {
|
||||
const component = createTestComponent('function hello() {\n return "hello";\n}');
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('hello()');
|
||||
|
||||
const expectedDetail = `function hello() {
|
||||
return 'hello'
|
||||
}`;
|
||||
|
||||
expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
|
||||
});
|
||||
|
||||
it('should support short element', () => {
|
||||
const component = createTestComponent('<div>Hey!</div>');
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('<div>Hey!</div>');
|
||||
expect(defaultValue.detail).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should support long element', () => {
|
||||
const component = createTestComponent(
|
||||
'() => {\n return <div>Inlined FunctionnalComponent!</div>;\n}'
|
||||
);
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('element');
|
||||
|
||||
const expectedDetail = `() => {
|
||||
return <div>Inlined FunctionnalComponent!</div>;
|
||||
}`;
|
||||
|
||||
expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
|
||||
});
|
||||
|
||||
it("should use the name of the React component when it's available", () => {
|
||||
const component = createTestComponent(
|
||||
'function InlinedFunctionalComponent() {\n return <div>Inlined FunctionnalComponent!</div>;\n}'
|
||||
);
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('<InlinedFunctionalComponent />');
|
||||
|
||||
const expectedDetail = `function InlinedFunctionalComponent() {
|
||||
return <div>Inlined FunctionnalComponent!</div>;
|
||||
}`;
|
||||
|
||||
expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
|
||||
});
|
||||
|
||||
it('should not use the name of an HTML element', () => {
|
||||
const component = createTestComponent('<div>Hey!</div>');
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).not.toBe('<div />');
|
||||
});
|
||||
|
||||
it('should support short array', () => {
|
||||
const component = createTestComponent('[1]');
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('[1]');
|
||||
expect(defaultValue.detail).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should support long array', () => {
|
||||
const component = createTestComponent(
|
||||
'[\n {\n thing: {\n id: 2,\n func: () => {},\n arr: [],\n },\n },\n]'
|
||||
);
|
||||
|
||||
const { defaultValue } = extractPropDef(component);
|
||||
|
||||
expect(defaultValue.summary).toBe('array');
|
||||
|
||||
const expectedDetail = `[{
|
||||
thing: {
|
||||
id: 2,
|
||||
func: () => {
|
||||
},
|
||||
arr: []
|
||||
}
|
||||
}]`;
|
||||
|
||||
expect(defaultValue.detail.replace(/\s/g, '')).toBe(expectedDetail.replace(/\s/g, ''));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('enhancePropTypesProps', () => {
|
||||
it('keep the original definition order', () => {
|
||||
it('should keep the original definition order', () => {
|
||||
const component = createComponent({
|
||||
propTypes: {
|
||||
foo: PropTypes.string,
|
||||
@ -751,4 +924,32 @@ describe('enhancePropTypesProps', () => {
|
||||
expect(props[2].name).toBe('bar');
|
||||
expect(props[3].name).toBe('endWithDefaultValue');
|
||||
});
|
||||
|
||||
it('should not include @ignore props', () => {
|
||||
const component = createComponent({
|
||||
propTypes: {
|
||||
foo: PropTypes.string,
|
||||
bar: PropTypes.string,
|
||||
},
|
||||
docgenInfo: {
|
||||
...createDocgenProp({
|
||||
name: 'foo',
|
||||
type: { name: 'string' },
|
||||
}),
|
||||
...createDocgenProp({
|
||||
name: 'bar',
|
||||
type: { name: 'string' },
|
||||
description: '@ignore',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const props = enhancePropTypesProps(
|
||||
extractPropsFromDocgen(component, DOCGEN_SECTION),
|
||||
component
|
||||
);
|
||||
|
||||
expect(props.length).toBe(1);
|
||||
expect(props[0].name).toBe('foo');
|
||||
});
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import { PropDef } from '@storybook/components';
|
||||
import { isNil } from 'lodash';
|
||||
import { Component } from '../../../blocks/shared';
|
||||
|
||||
// react-docgen doesn't returned the props in the order they were defined in the "propTypes" of the component.
|
||||
// react-docgen doesn't returned the props in the order they were defined in the "propTypes" object of the component.
|
||||
// This function re-order them by their original definition order.
|
||||
export function keepOriginalDefinitionOrder(
|
||||
extractedProps: PropDef[],
|
||||
@ -12,7 +12,9 @@ export function keepOriginalDefinitionOrder(
|
||||
const { propTypes } = component;
|
||||
|
||||
if (!isNil(propTypes)) {
|
||||
return Object.keys(propTypes).map(x => extractedProps.find(y => y.name === x));
|
||||
return Object.keys(propTypes)
|
||||
.map(x => extractedProps.find(y => y.name === x))
|
||||
.filter(x => x);
|
||||
}
|
||||
|
||||
return extractedProps;
|
||||
|
@ -39,7 +39,7 @@ componentNotes.story.parameters = { mdxSource: '<Button>Component notes</Button>
|
||||
|
||||
const componentMeta = { title: 'Button', id: 'button-id', includeStories: ['componentNotes'] };
|
||||
|
||||
const mdxStoryNameToId = { 'component notes': 'button--component-notes' };
|
||||
const mdxStoryNameToId = { 'component notes': 'button-id--component-notes' };
|
||||
|
||||
componentMeta.parameters = componentMeta.parameters || {};
|
||||
componentMeta.parameters.docs = {
|
||||
|
@ -298,11 +298,11 @@ function extractExports(node, options) {
|
||||
}
|
||||
metaExport.includeStories = JSON.stringify(includeStories);
|
||||
|
||||
const { title } = metaExport;
|
||||
const { title, id: componentId } = metaExport;
|
||||
const mdxStoryNameToId = Object.entries(context.storyNameToKey).reduce(
|
||||
(acc, [storyName, storyKey]) => {
|
||||
if (title) {
|
||||
acc[storyName] = toId(title, storyNameFromExport(storyKey));
|
||||
acc[storyName] = toId(componentId || title, storyNameFromExport(storyKey));
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
|
17
examples/official-storybook/components/ButtonGroup.js
Normal file
17
examples/official-storybook/components/ButtonGroup.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/** ButtonGroup component description from docgen */
|
||||
export const ButtonGroup = ({ background, children }) => (
|
||||
<div style={{ background }}>{children}</div>
|
||||
);
|
||||
|
||||
ButtonGroup.defaultProps = {
|
||||
background: '#ff0',
|
||||
children: null,
|
||||
};
|
||||
|
||||
ButtonGroup.propTypes = {
|
||||
background: PropTypes.string,
|
||||
children: PropTypes.arrayOf(PropTypes.element),
|
||||
};
|
@ -56,12 +56,6 @@ addParameters({
|
||||
{ name: 'dark', value: '#222222' },
|
||||
],
|
||||
docs: {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
page: ({ context }) => (
|
||||
<DocsPage
|
||||
context={context}
|
||||
subtitleSlot={({ selectedKind }) => `Subtitle: ${selectedKind}`}
|
||||
/>
|
||||
),
|
||||
page: () => <DocsPage subtitleSlot={({ selectedKind }) => `Subtitle: ${selectedKind}`} />,
|
||||
},
|
||||
});
|
||||
|
@ -0,0 +1,176 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Title,
|
||||
Subtitle,
|
||||
Description,
|
||||
Primary,
|
||||
Props,
|
||||
Stories,
|
||||
} from '@storybook/addon-docs/blocks';
|
||||
import { DocgenButton } from '../../components/DocgenButton';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import { ButtonGroup } from '../../components/ButtonGroup';
|
||||
|
||||
export default {
|
||||
title: 'Addons/Docs/stories docs bocks',
|
||||
component: DocgenButton,
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Title />
|
||||
<Subtitle />
|
||||
<Description />
|
||||
<Primary />
|
||||
<Props />
|
||||
<Stories />
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const defDocsPage = () => <div>Default docs page</div>;
|
||||
|
||||
export const smallDocsPage = () => <div>Just primary story, </div>;
|
||||
smallDocsPage.story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Title />
|
||||
<Primary />
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const checkBoxProps = () => <div>Primary props displayed with a check box </div>;
|
||||
checkBoxProps.story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => {
|
||||
const [showProps, setShowProps] = React.useState(false);
|
||||
return (
|
||||
<>
|
||||
<Title />
|
||||
<Subtitle />
|
||||
<Description />
|
||||
<Primary />
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showProps}
|
||||
onChange={() => setShowProps(!showProps)}
|
||||
/>
|
||||
<span>display props</span>
|
||||
</label>
|
||||
{showProps && <Props />}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const customLabels = () => <div>Display custom title, Subtitle, Description</div>;
|
||||
customLabels.story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Title>Custom title</Title>
|
||||
<Subtitle>Custom sub title</Subtitle>
|
||||
<Description>Custom description</Description>
|
||||
<Primary />
|
||||
<Props />
|
||||
<Stories title="Custom stories title" />
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const customStoriesFilter = () => <div>Displays ALL stories (not excluding first one)</div>;
|
||||
customStoriesFilter.story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Stories slot={stories => stories} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const descriptionSlot = () => <div>Adds markdown to the description</div>;
|
||||
descriptionSlot.story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Description slot={description => `<b>${description}</b>`} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const multipleComponents = () => (
|
||||
<ButtonGroup>
|
||||
<DocgenButton label="one" />
|
||||
<DocgenButton label="two" />
|
||||
<DocgenButton label="three" />
|
||||
</ButtonGroup>
|
||||
);
|
||||
|
||||
multipleComponents.story = {
|
||||
name: 'Many Components',
|
||||
parameters: {
|
||||
component: ButtonGroup,
|
||||
subcomponents: {
|
||||
'Docgen Button': DocgenButton,
|
||||
'Base Button': BaseButton,
|
||||
},
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Title />
|
||||
<Subtitle />
|
||||
<Description />
|
||||
<Primary slot={stories => stories.find(story => story.story === 'Many Components')} />
|
||||
<Props />
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const componentsProps = () => <div>Display multiple prop tables in tabs</div>;
|
||||
componentsProps.story = {
|
||||
subcomponents: {
|
||||
'Docgen Button': DocgenButton,
|
||||
'Base Button': BaseButton,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
page: () => (
|
||||
<>
|
||||
<Title>Multiple prop tables</Title>
|
||||
<Description>
|
||||
Here's what happens when your component has some related components
|
||||
</Description>
|
||||
<Props
|
||||
components={{
|
||||
'ButtonGroup Custom': ButtonGroup,
|
||||
'Docgen Button': DocgenButton,
|
||||
'Base Button': BaseButton,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { DocsPage, DocsWrapper, DocsContent } from './DocsPage';
|
||||
import { Title, Subtitle, DocsWrapper, DocsContent } from './DocsPage';
|
||||
import * as storyStories from './Story.stories';
|
||||
import * as previewStories from './Preview.stories';
|
||||
import * as propsTableStories from './PropsTable/PropsTable.stories';
|
||||
@ -8,7 +8,7 @@ import * as descriptionStories from './Description.stories';
|
||||
|
||||
export default {
|
||||
title: 'Docs/DocsPage',
|
||||
component: DocsPage,
|
||||
component: DocsWrapper,
|
||||
decorators: [
|
||||
storyFn => (
|
||||
<DocsWrapper>
|
||||
@ -19,49 +19,53 @@ export default {
|
||||
};
|
||||
|
||||
export const withSubtitle = () => (
|
||||
<DocsPage
|
||||
title="DocsPage"
|
||||
subtitle="What the DocsPage looks like. Meant to be QAed in Canvas tab not in Docs tab."
|
||||
>
|
||||
<>
|
||||
<Title>DocsPage</Title>
|
||||
<Subtitle>
|
||||
What the DocsPage looks like. Meant to be QAed in Canvas tab not in Docs tab.
|
||||
</Subtitle>
|
||||
{descriptionStories.text()}
|
||||
{previewStories.single()}
|
||||
{propsTableStories.normal()}
|
||||
{sourceStories.jsx()}
|
||||
</DocsPage>
|
||||
</>
|
||||
);
|
||||
withSubtitle.story = { name: 'with subtitle' };
|
||||
|
||||
export const empty = () => (
|
||||
<DocsPage title={null}>
|
||||
<>
|
||||
{storyStories.error()}
|
||||
{propsTableStories.error()}
|
||||
{sourceStories.sourceUnavailable()}
|
||||
</DocsPage>
|
||||
</>
|
||||
);
|
||||
|
||||
export const noText = () => (
|
||||
<DocsPage title="no text">
|
||||
<>
|
||||
<Title>no text</Title>
|
||||
{previewStories.single()}
|
||||
{propsTableStories.normal()}
|
||||
{sourceStories.jsx()}
|
||||
</DocsPage>
|
||||
</>
|
||||
);
|
||||
noText.story = { name: 'no text' };
|
||||
|
||||
export const text = () => (
|
||||
<DocsPage title="Sensorium">
|
||||
<>
|
||||
<Title>Sensorium</Title>
|
||||
{descriptionStories.text()}
|
||||
{previewStories.single()}
|
||||
{propsTableStories.normal()}
|
||||
{sourceStories.jsx()}
|
||||
</DocsPage>
|
||||
</>
|
||||
);
|
||||
|
||||
export const markdown = () => (
|
||||
<DocsPage title="markdown">
|
||||
<>
|
||||
<Title>markdown</Title>
|
||||
{descriptionStories.markdown()}
|
||||
{previewStories.single()}
|
||||
{propsTableStories.normal()}
|
||||
{sourceStories.jsx()}
|
||||
</DocsPage>
|
||||
</>
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ export interface DocsPageProps {
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
const Title = styled.h1<{}>(withReset, ({ theme }: { theme: Theme }) => ({
|
||||
export const Title = styled.h1<{}>(withReset, ({ theme }: { theme: Theme }) => ({
|
||||
color: theme.color.defaultText,
|
||||
fontSize: theme.typography.size.m3,
|
||||
fontWeight: theme.typography.weight.black,
|
||||
@ -24,7 +24,7 @@ const Title = styled.h1<{}>(withReset, ({ theme }: { theme: Theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const Subtitle = styled.h2<{}>(withReset, ({ theme }: { theme: Theme }) => ({
|
||||
export const Subtitle = styled.h2<{}>(withReset, ({ theme }: { theme: Theme }) => ({
|
||||
fontWeight: theme.typography.weight.regular,
|
||||
fontSize: theme.typography.size.s3,
|
||||
lineHeight: '20px',
|
||||
@ -62,18 +62,3 @@ export const DocsPageWrapper: FunctionComponent = ({ children }) => (
|
||||
<DocsContent>{children}</DocsContent>
|
||||
</DocsWrapper>
|
||||
);
|
||||
|
||||
/**
|
||||
* An out-of-the box documentation page for components that shows the
|
||||
* title & subtitle and a collection of blocks including `Description`,
|
||||
* and `Preview`s for each of the component's stories.
|
||||
*/
|
||||
const DocsPage: FunctionComponent<DocsPageProps> = ({ title, subtitle, children }) => (
|
||||
<>
|
||||
{title && <Title className="sbdocs-title">{title}</Title>}
|
||||
{subtitle && <Subtitle className="sbdocs-subtitle">{subtitle}</Subtitle>}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
export { DocsPage };
|
||||
|
Loading…
x
Reference in New Issue
Block a user