Custom documentation pages for storybookjs

Atanas Stoyanov
5 min readApr 28, 2020

--

This is an early release software, (mostly) working with alpha releases of storybook6. Use at your own risk :)

Overview

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

custom documentation pages 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 Storybok 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.
  2. 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 template

2. 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 CSF 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;
custom story render

3. 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 addon-docs blocks

4. 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

5. 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 addon-docs and component-controls

--

--