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
and
Design Notes
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'
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:
we don't see the name
intro1
anywhere, and a.f.a.i.k. this is as expected behaviorwe get to see the new
MAIN
section in the menu tree on the left andIntro2
as a new menu optionfinally,
intro3
will be in the text as expected:
Using more pipes (
|
) in thetitle
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:
And this will be our Docs
part:
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
!