Storybook React - DocsPage+MDX and Notes+MD with Typescript

TL;DR When I came across the Storybook project as a way to create a standard design system, I have very quickly realized that many of the code snippets in the official documentation don't work out of the box if using Typescript. My focus however was on setting up the Storybook DocsPage + MDX and Storybook Notes + Markdown with Typescript. In this blog post you can read how I got everything working. First, Notes with Markdown. Then DocsPage with MDX.


Keep in mind#

In the beginning, when I added Typescript many things broke and stopped working, so I have had to search on Google and have tried a whole bunch of different tips and tricks. Of course, most of them come from the Storybook documentation, Storybook blog posts, and StackOverflow. By the time I got stuff working, I did not know any more, which tip I found where. Even if everything I added was really necessary. So feel free to remove some additions if you think they don't make sense.

We are using YARN all over the project, so you will see a lot of yarn commands. If you prefer NPM, just replace yarn add with npm i to install stuff, and yarn <some script from package.json> with npm run <same script from package.json> and you are good to go. :-)

Typescript stuff#

Here is the tsconfig.json in the root of the project:

{
"compilerOptions": {
"allowJs": false,
"allowSyntheticDefaultImports": true,
"downlevelIteration": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": false,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "esnext",
"typeRoots": [
"./node_modules/@types",
"./src/**/*.d.ts"
]
},
"exclude": [
"build",
"node_modules",
"**/*.spec.ts",
"**/*.spec.tsx"
],
"include": [
"src/**/*"
]
}

This is the tsconfig.json in the .storybook folder:

{
"extends": "../tsconfig.json",
"include": [
"../src/**/*",
"./storybookTheme.ts"
]
}

Add react.d.ts typings file in src/types/@storybook folder:

export const addDecorator: any
export const addParameters: any
export const configure: any
export const forceReRender: any
export const getStorybook: any
export const raw: any
export const setAddon: any
export const storiesOf: any

Notes with Markdown#

For the Storybook Notes we will need raw-loader and @storybook/addon-notes NPM packages:

yarn add -D raw-loader @storybook/addon-notes

Register the addon in .storybook/addons.js:

import '@storybook/addon-notes/register'

Next, suppose you have a Button.stories.tsx file. Place a notes.md file next to it:

__DocsPage__ is a new Storybook way of generating documentation (see the __Docs__ tab).
However, we can also add extra __Notes__ here in plain __Markdown__.
### Lo and behold!
``` js
console.log('Hello world!')
const bs = ({ arg }) => {
if (arg === 0) {
return false
}
}
```

Add raw-loader to the .storybook/webpack.config.js configuration:

module: {
rules: [
...
{
test: /\.md$/,
use: 'raw-loader',
},
],
},

Now, in Button.stories.tsx add this code:

... // add here your imports, etc.
const notes = require('!!raw-loader!./notes.md').default
... // add more const if necessary
storiesOf('Button', module)
... // add here your decorators
.add(
'default',
() => {
return (
... // add here component details
)
...
},
{
notes: {
Information: 'This page can be used for the general audience',
'Design Notes': notes,
},
}
)

Now, if your start your storybook with yarn start, you will see by the Button component the Notes panel with two tabs:

Information#

notes panel with two tabs - information tab

and

Design Notes#

notes panel with two tabs - design notes tab

The words Information and Design Notes here are arbitrary. You can use any other words instead. The only exception a.f.a.i.k. is the word markdown, which has a special meaning as a keyword in Storybook. I must say, however, that I have had issues trying to implement Notes with this markdown option. E.g., single quotes would be replaced with &#39; on the screen.

DocsPage with MDX#

Pre-requisites#

For the Storybook DocsPage + MDX we will need @mdx-js/loader, babel-loader and @storybook/addon-docs NPM packages:

yarn add -D @mdx-js/loader babel-loader @storybook/addon-docs

Register the addon in .storybook/addons.js:

import '@storybook/addon-docs/register'

Add the following configurations to the .storybook/config.js file:

import { DocsContainer, DocsPage } from '@storybook/addon-docs/blocks'
import { addDecorator, addParameters, configure } from '@storybook/react'
... // add here other imports
... // add here your decorators
addParameters({
options: {
sortStoriesByKind: true,
... // add here other options
},
docs: {
container: DocsContainer,
page: DocsPage,
},
})
configure(
[
require.context('../stories', true, /\.stories\.tsx?$/),
require.context('../stories', true, /\.stories\.js$/),
require.context('../stories', true, /\.stories\.mdx$/),
],
module
)

Add the following to the .storybook/presets.js file:

module.exports = ['@storybook/addon-docs/react/preset']

And this goes into .storybook/webpack.config.js:

const createCompiler = require('@storybook/addon-docs/mdx-compiler-plugin')
... // add here your other const
module.exports = async ({ config, mode }) => ({
...config,
resolve: {
...config.resolve,
extensions: ['.js', '.jsx', '.ts', '.tsx', 'md', 'mdx'],
},
... // add here your other settings
module: {
rules: [
... // add here other extensions
{
test: /\.(stories)\.mdx$/,
use: [
{
loader: 'babel-loader',
},
{
loader: '@mdx-js/loader',
options: {
compilers: [createCompiler({})],
},
},
],
},
]
}
})

Now, let us start experimenting with MDX.

One important observation though. The names of the MDX files we wish to see in our Storybook should end with .stories.mdx, not just .mdx.

Where the different names go#

Let us create our first file:

import { Meta } from '@storybook/addon-docs/blocks'
<Meta title='Main|Intro2' />
# INTRO3
Most of the documentation can be done via the __Docs__ option.
Yet, in some cases we can add side notes in __Markdown__.
```js
console.log('this is an Intro page!')
const test = ({ arg }) => {
if (true) {
return false
}
}
```

Here, I have used intro1 in the name of the file, intro2 in the meta tag, and intro3 in the text. When we now run yarn start, we will notice the following:

  1. we don't see the name intro1 anywhere, and a.f.a.i.k. this is as expected behavior

  2. we get to see the new MAIN section in the menu tree on the left and Intro2 as a new menu option

    main menu with intro2 option

  3. finally, intro3 will be in the text as expected:

    docs panel with intro3 in the text

Using more pipes (|) in the title property <Meta /> tag can be used as a means to create the complete documentation hierarchy next to the UI-components themselves.

MDX file using a React component#

In the previous section the .mdx file was in fact a pure .md file. Now, let us add a second file but this time let us add a React component inside. For this example, we will need to install moment.js package to format dates:

yarn add moment

and create a simple CommitDate React component in src/components folder:

import moment from 'moment'
import * as React from 'react'
interface IDate {
date: string
}
export const CommitDate = ({ date }: IDate): React.ReactNode => {
console.log(date)
const formattedDate = moment(date).format('LL')
console.log(formattedDate)
return (
<>
<em>{formattedDate}</em>
</>
)
}

Now, we create the changelog page: changelog.stories.mdx.

import moment from 'moment'
import { Meta } from '@storybook/addon-docs/blocks'
import { CommitDate } from '../src/Components'
<Meta title='Main|Changelog' />
# Changelog
| Version | Date | Description |
| ---------- | ---------------------------------- | ----------------- |
| __1.2.18__ | <CommitDate date={'2019-09-05'} /> | ✨ Initial release |

Let us run the Storybook UI with yarn start. First of all, we will get the second menu option under the MAIN menu section:

main menu with changelog and intro2

And this will be our Docs part:

commit_date_mdx_example

As you can see, the Date column gets the formatted commit date:

September 5, 2019.

QED

Concluding notes#

In my personal experience, adding Typescript - even to a working - project can lead to quite a lot of extra wiring and testing. Storybook projects are not an exception. Most of the code snippets in the documentation are written in ES6 en use not types. Just installing the Typescript related NPM packages and changing the file extensions from .js/.jsx to .ts/.tsx is not the end but only the beginning of the refactoring road.

In this blog post, I have shown the steps that were necessary to get the Storybook DocsPage working with MDX stories and Notes with Markdown files. I would be happy if this information helps you to fix your Storybook project.

By the way, it looks that Storybook is going to replace Notes with DocsPage. E.g., Notes is not mentioned any more in the list of add-ons on the Storybook Add-ons page. Well the Docs!

References#

Articles about the DocsPage and the Storybook Design System#

Sources#