A next-generation tool to create blazing-fast documentation sites
API
created:4/27/2020
,
updated:11/6/2020

Overview

@component-controls/storybook-custom-docs makes it possible to add an unlimited number of custom documentation pages to storybook.

integrated in storybook

Demo sites

Background

The Storybook addon-docs is a great feature to display documentation in Storybook, unfortunately the early versions (5.x and 6.x as of this writing) have a few limitations, amongst them is that there can be only one 'docs' page.

In order to have multiple, fully functional documentation pages, we had to solve the following challenges:

  1. Circumvent the hard-coded docs render.

docs pages need to reside in the preview part of Storybook in order to render stories (since that's where the stories render functions reside), while the TAB addons are placed in the manager part of storybook. Since the manager and the preview reside in different bundles, only JSON-compatible data is available to any code residing in the manager, thus any functions are not available.

  1. Circumvent the hard-coded DOM elements

documentation pages that render stories need to reside inside the preview iframe in order to render stories in a custom docs page and prevent CSS styles leaking into the story functions, while regular storybook TAB-type addons are rendered outside the iframe.

Step by step guide

  • Getting started

install the addon:

yarn add @component-controls/storybook-custom-docs

in .storybook/main.js - enable the plugin and configure the pages options parameter.

module.exports = {
...
addons: [
...
{
name: '@component-controls/storybook-custom-docs',
options: {
//configure an array of the pages to display
pages: [require.resolve('./page-story.js')]
},
}
],
};
  • Page templates the custom page template files must have a default export with the following fields
{
//the url path for the page. e.g: 'page'
key: string,
//the tab title. e.g: 'My Pages'
title: string,
//render function, return your custom page here.
render: ({ active }) => React.ReactNode,
}

Examples

  1. From component-controls/pages

Component-controls comes with a handy selection of page templates that you can use as a starting point. The only requirement is to enclose the pages in a DocsContainer context from @component-controls/storybook

import React from 'react';
import { ClassicPage } from '@component-controls/pages';
import { DocsContainer } from '@component-controls/storybook';
export default {
key: 'page',
title: 'Page',
render: ({ active }) => active ? (
<DocsContainer active={active}>
<ClassicPage />
</DocsContainer>
): null,
};

component-controls page

  1. Custom render the current story

You can create docs pages from the grounds up and to render the stories, you can use the context.storyFn which is the equivalent of the story decorated esm export.

import React, { createElement } from 'react';
import { useContext } from '@component-controls/storybook-custom-docs';
const CustomPage = () => {
const context = useContext();
return (
<div>
<h1>Simple docs page</h1>
{createElement(context.storyFn)}
</div>
);
}
const page = {
key: 'custom',
title: 'Simple Page',
render: ({ active }) => active ? <CustomPage /> : null,
}
export default page;

simple custom page

  1. Storybook addon-docs blocks

In order to embed storybook's addon-docs block elements, you need to import them from @storybook/addon-docs/blocks and enclose them in a DocsContainer container:

import React from 'react';import { DocsContainer, Story, Preview, Source, Title } from '@storybook/addon-docs/blocks';
import { useContext } from '@component-controls/storybook-custom-docs';
const Page = () => {
const context = useContext();
return (
<DocsContainer context={context}>
<Title>Using storybook docs page blocks</Title>
<Preview>
<Story id="." />
</Preview>
<Source id="." />
</DocsContainer>
);
}
const page = {
key: 'docs-page',
title: 'Docs blocks',
render: ({ active }) => active ? <Page /> : null}
export default page;

storybook docs blocks

  1. Component-controls blocks

In order to embed component-controls own blocks, you need to enclose them in a DocsContainer imported from '@component-controls/storybook' and the basic blocks are to be imported from '@component-controls/blocks'

import React from 'react';
import { DocsContainer } from '@component-controls/storybook';
import { Story, Title, Playground } from '@component-controls/blocks';
const Page = ({ active }) => (
<DocsContainer active={active} >
<Title>Component controls blocks</Title>
<Playground openTab="source" title=".">
<Story id="." />
</Playground>
</DocsContainer>
);
const page = {
key: 'component-page',
title: 'Controls blocks',
render: ({ active }) => active ? <Page /> : null
}
export default page;

component-controls blocks

  1. Mixed blocks

You can even create documentation pages with a mix of storybook and component-controls block components:

import React from 'react';
import { DocsContainer as SBDocsContainer, Preview, Story as SBStory, Title as SBTitle, Props} from '@storybook/addon-docs/blocks';
import { useContext } from '@component-controls/storybook-custom-docs';
import { DocsContainer } from '@component-controls/storybook';
import { Story, Title, Playground, PropsTable } from '@component-controls/blocks';
const Page = () => {
const context = useContext();
return (
<>
<h1>Mixing storybook docs blocks and component-controls blocks</h1>
<SBDocsContainer context={context}>
<SBTitle />
<Preview >
<SBStory id={context.storyId} />
</Preview>
<Props of='.' />
</SBDocsContainer>
<DocsContainer storyId={context.storyId}>
<Title />
<Playground openTab="source" title="." dark={true}>
<Story id="." />
</Playground>
<PropsTable of="." />
</DocsContainer>
</>
);
}
const page = {
key: 'mixed-page',
title: 'Mixed blocks',
render: ({ active }) => active ? <Page /> : null,
}
export default page;

mixed blocks