Storybook Configuration With CRA and TypeScript
![storybook configuration](https://pagepro.co/blog/wp-content/uploads/2021/05/Storybook-configuration-630x358.png)
Intro
If you want to start a Storybook dvelopment, you should do this with configuration. Let me show you step-by-step how to do it based on create-react-app and TypeScript example.
CRA:
Base configuration:
You can add a storybook to other frameworks like Gatsby.js, Next.js, etc. as well, but in this example, we will just use a simple CRA project with TypeScript.
Here’s what you have to do:
- Go to: https://create-react-app.dev/docs/adding-typescript/ to check the documentation (optional).
- To create a react app type the command below into your terminal:
yarn create react-app storybook-app --template typescript
Hint: --template typescript
adds typescript configuration to the project out of the box. You can configure tsconfig.json later with the properties that you need (if you know what you’re doing of course).
Go to your projects directory and type this in your terminal to run the app:
yarn run start
It should automatically open on port 3000.
Styles:
And pretty much that’s all when it comes to project base configuration.
You may also want to add a few libraries that can help you with CSS, like Styled Components for example, so the next steps are:
- Add styled-components (or other library to prepare styles) to the project:
yarn add styled-components
yarn add -D @types/styled-components
Hint: dependencies are required to run, devDependencies only to develop, e.g: unit tests, CoffeeScript to JavaScript transpilation, minification, …
- You can also add CSS normalizer if you want:
yarn add styled-normalize
Project structure:
We’re going to use Atomic Design approach in this example, so we will need to create 3 directories inside src/components:
- atoms
- molecules
- organisms
We can also add a directory called styles to store components used for typography, layout, etc.
Storybook
When the project base is set, we can finally add Storybook – it’s required to have a project base first, you cannot just run storybook without it:
Storybook needs to be installed into a project that is already setup with a framework. It will not work on an empty project (Storybook info)
Installation:
Type the command below in your terminal (inside a directory with your project base created earlier with CRA – it should be in the root of this directory):
npx sb init
This will install all the dependencies needed.
You don’t even have to install any dependencies to use TypeScript with Storybook – everything is already there.
Run:
yarn run storybook
To see Storybook default view with some demo components added.
Storybook directory structure:
When you will open .storybook directory, you will see 2 files added:
- main.js
- preview.js
You can also add a file called:
- manager.js (optional)
main.js
This file controls the behavior of the Storybook server – you must restart Storybook’s process when you change it. It is composed of four key fields:
- stories – define a path to the stories that you want to add to the Storybook
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
]
- addons – register the addons that you will use for writing stories (https://storybook.js.org/docs/react/api/addons)
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/preset-create-react-app"
]
- webpackFinal – custom webpack configuration.
- babel – custom babel configuration.
preview.js
To control the way stories are rendered and add global decorators and parameters, create a .storybook/preview.js file. This is loaded in the Canvas tab, the “preview” iframe that renders your components in isolation. Use preview.js for global code (such as CSS imports or JavaScript mocks) that applies to all stories. (Storybook)
It exports the following keys:
- decorators – an array of global decorators
export const decorators = [
(Story) => (
<>
<Normalize />
<Story />
</>
),
];
- parameters – an object of global parameters
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
};
- globalTypes – definition of globalTypes
manager.js – optional
You can also add manager.js file to control the behaviour of Storybook’s UI (including Storybook theme)
import { addons } from '@storybook/addons';
import { themes } from '@storybook/theming';
addons.setConfig({
theme: themes.dark,
});
Want to use Storybook in your project?
Stories:
File name
Inside a directory with the component that you created, and you want to display on Storybook you have to add a file which name starts with components’ name and ends with .stories.tsx
Button.stories.tsx
File content
Base
Base content looks like this:
import React from "react";
import { Meta, Story } from "@storybook/react/types-6-0";
import Button from ".";
export default {
title: "Atoms/Button",
component: Button,
} as Meta;
export const Base: Story = () => <Button>Button content</Button>;
- title – the name of the section where our component will be visible
- when adding a name with “/” it creates a subsection. In this example Atoms will be the main section and Button will be added under it as its subsection
- component – a component that we want to display
export const Base: Story = () => <Button>Button content</Button>;
- Base is the name of our story
- if we have more examples to show, we can add multiple story components with different names – like Primary, Secondary, etc.:
export const Primary = () => <Button background="#ff0" label="Button" />;
export const Secondary = () => <Button background="#ff0" label="😄👍😍💯" />;
export const Tertiary = () => <Button background="#ff0" label="📚📕📈🤓" />;
Template
We can also create a template for our components stories if there are some common props that we want to show. We can then add a story for each variant and apply only the needed props without repeating any other props.
const Template: Story<ButtonProps> = (args) => <Button {...args} />;
After preparing a Template we can use it like this:
export const Primary = Template.bind({});
Primary.args = {
mode: ButtonMode.primary,
children: "Button content",
};
export const Secondary = Template.bind({});
Secondary.args = {
mode: ButtonMode.secondary,
children: "Button content",
};
Controls
Previously there was an addon called knobs – it allowed to edit some content of the component (its props, etc.) in the Storybook app, so you could change the text inside a button to check how it behaves with less or more text inside, make it disabled, etc.
The successor of knobs is an addon called controls. It has similar functionalities, but we have to define editable props first with args or argTypes. We can use such types:
Data Type | Control Type | Description | Options |
array | array | serialize array into a comma-separated string inside a textbox | separator |
boolean | boolean | checkbox input | – |
number | number | a numeric text box input | min, max, step |
range | a range slider input | min, max, step | |
object | object | json editor text input | – |
enum | radio | radio buttons input | options |
inline-radio | inline radio buttons input | options | |
check | multi-select checkbox input | options | |
inline-check | multi-select inline checkbox input | options | |
select | select dropdown input | options | |
multi-select | multi-select dropdown input | options | |
string | text | simple text input | – |
color | color picker input that assumes strings are color values | – | |
date | date picker input | – |
We can use it like this:
export default {
title: "Atoms/Button",
component: Button,
argTypes: {
disabled: {
name: "Is disabled",
control: {
type: "boolean",
},
defaultValue: false,
},
mode: {
table: { disable: true },
},
children: {
name: "Content",
control: {
required: true,
},
},
size: {
name: "Size",
control: {
type: "select",
options: [ButtonSize.big, ButtonSize.medium, ButtonSize.small],
},
defaultValue: ButtonSize.big,
},
},
} as Meta;
We used boolean and select in this example. children prop don’t have a control type defined and it renders text field. If there is a props that we don’t want to display we can set it just like mode:
mode: {
table: { disable: true },
},
When adding other variants we might need to change just one prop, to do so we can just modify it with args.
export const Primary = Template.bind({});
Primary.args = {
mode: ButtonMode.primary,
children: "Button content",
};
Actions
Components like buttons always have some onClick actions passed to them.
We can also simulate those actions in Storybook using Actions addon:
export default {
title: "Atoms/Button",
component: Button,
argTypes: {
onClick: { action: 'clicked' }
},
} as Meta;
- action – what should be displayed after onClick action
It is also possible to detect if your component is emitting the correct HTML events using the parameters.actions.handles parameter.
export default {
title: 'Button',
parameters: {
actions: {
handles: ['mouseover', 'click .btn'],
},
},
};
Backgrounds
Some components are light and need a dark background, some are dark and need a light background, and it’s also possible to set a needed background for each component. We can set it to every variant of the component in the story or a different one for each of them. Unfortunately there is a small bug right now that makes it impossible to use this addon without a small fix. To make it work we have to add:
.sb-show-main {
background-color: transparent;
}
to Storybook styles.
After that we will be able to use it like this:
export default {
title: "Atoms/Button",
component: Button,
parameters: {
backgrounds: {
default: "light",
values: [
{ name: "light", value: "#fff" },
{ name: "dark", value: "#000" },
],
},
},
} as Meta;
or like this (for single variant):
Secondary.parameters = {
backgrounds: { default: 'light' }
};
Decorators
There is also an option to add some custom wrapper to each story. To do so we have to define a decorator:
export default {
component: Button,
decorators: [(Story) => <div style={{ margin: '3em' }}><Story/></div>]
}
To change the component position we can use a parameter called layout.
It has the options to chose:
- centered: center the component horizontally and vertically in the Canvas
- fullscreen: allow the component to expand to the full width and height of the Canvas
- padded: Add extra padding around the component
export default {
title: "Atoms/Button",
component: Button,
parameters: {
layout: "centered",
}
} as Meta;
Hierarchy:
Grouping
We can group some components using / just like in the example with the Button where we grouped it with Atoms
export default {
title: "Atoms/Button",
component: Button,
} as Meta;
There is also a possibility to add more layers inside it, so there can be for example:
title: "Atoms/Button/Square",
etc.
By default, the top-level grouping will be displayed as a “root” in the UI (the all-caps, non-expandable grouping in the screenshot above). If you prefer, you can configure Storybook to not show roots.
Sorting stories
By default, stories are sorted in the order in which they were imported. This can be overridden by adding storySort to the options parameters in your preview.js file.
export const parameters = {
options: {
storySort: (a, b) =>
a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }),
},
};
or
export const parameters = {
options: {
storySort: {
method: '',
order: [],
locales: '',
},
},
};
Field | Type | Description | Required | Default Value | Example |
method | String | Tells Storybook in which order the stories are displayed | No | Storybook configuration | ‘alphabetical’ |
order | Array | The stories to be shown, ordered by supplied name | No | Empty Array [ ] | [‘Intro’, ‘Components’] |
locales | String | The locale required to be displayed | No | System locale | en-US |
We’re using Atomic Design approach, so let’s use it also when defying stories sort order:
export const parameters = {
options: {
storySort: {
order: ['Styles', 'Atoms', 'Molecules', 'Organisms'],
},
}
};
Our sections will always be rendered in a defined order. If a new section that isn’t defined here will be added, it will be displayed after all the defined sections.