Storybook

A UI development tool to develop and test components isolated.

Installation

To install Storybook you will only need to run:

npm i --save-dev @storybook/react @types/storybook__react awesome-typescript-loader react-docgen-typescript-webpack-plugin

See their official web page for more.

Plugins

In Storybook we have something called addons.

  • actions: can be used to display data received by event handlers.

  • info: will show additional information for stories.

  • knobs: allow you to edit React props dynamically using the Storybook UI.

  • jsx: shows the JSX of the component.

All of them can be installed by running:

npm i --save-dev @storybook/addon-actions @types/storybook__addon-actions
npm i --save-dev @storybook/addon-info @types/storybook__addon-info
npm i --save-dev @storybook/addon-knobs @types/storybook__addon-knobs
npm i --save-dev storybook-addon-jsx

Configuration

We will proceed to create a config folder only for Storybook in the root of our project. It will look like this:

.storybook
├── addons.js
├── config.js
├── style.js
└── webpack.config.js

addons.js file will import all the addons we need for Storybook:

import "@storybook/addon-options/register";
import "@storybook/addon-knobs/register";
import "@storybook/addon-actions/register";
import 'storybook-addon-jsx/register';

The config.js file will contain all the configuration. Is the one responsible to load all the stories:

import { configure, addDecorator, setAddon } from "@storybook/react";
import "../src/styles.css";
import { setOptions } from "@storybook/addon-options";
import { withKnobs } from "@storybook/addon-knobs";
import JSXAddon from 'storybook-addon-jsx';

setAddon(JSXAddon);

// Load all the stories
const req = require.context("../src/components", true, /\.story\.tsx$/);

function loadStories() {
    req.keys().forEach(filename => req(filename));
}

// Default decorators
addDecorator(withKnobs);

// Addon-options configuration
setOptions({
    name: "shark-events-client",
    addonPanelInRight: false,
    sortStoriesByKind: true,
    sidebarAnimations: false
});

configure(loadStories, module);

Finally, we will need to set our webpack.config.js file. Because we are using TypeScript, our info addon will need to import a plugin called react-docgen-typescript-webpack-plugin:

const path = require("path");
const TSDocgenPlugin = require("react-docgen-typescript-webpack-plugin");
module.exports = (baseConfig, env, config) => {
    config.module.rules.push({
        test: /\.(ts|tsx)$/,
        loader: require.resolve("awesome-typescript-loader")
    });
    config.plugins.push(new TSDocgenPlugin()); // optional
    config.resolve.extensions.push(".ts", ".tsx");
    return config;
};

JSX addon does not have types. TypeScript will complain once you add it to a component.

To solve this, create a new dile inside your project in the following path src/storybook.d.ts:

import { RenderFunction } from "@storybook/react";

declare module "@storybook/react" {
    export interface Story {
        addWithJSX(storyName: string, callback: RenderFunction): this;
    }
}

Component example

In our application we have a button component:

import { boolean, select, text } from "@storybook/addon-knobs";
import { storiesOf } from "@storybook/react";
import React from "react";
import { EditIcon } from "../icon";
import { APPEARANCE_DEFAULT } from "../lozenge/lozenge.constant";
import { Button } from "./button";
import { BUTTON_APPEARANCE_DEFAULT, BUTTON_APPEARANCE_PRIMARY, BUTTON_APPEARANCE_TRANSPARENT } from "./button.constant";

const appearanceOptions = {
    default: BUTTON_APPEARANCE_DEFAULT,
    transparent: BUTTON_APPEARANCE_TRANSPARENT,
    primary: BUTTON_APPEARANCE_PRIMARY
};

storiesOf("Button", module).addWithJSX("as dynamic variables", () => {
    const placeholder = text("Text", "Lorem");
    const appearance = select("Appearance", appearanceOptions, APPEARANCE_DEFAULT);
    const isDisabled = boolean("Is disabled", false);
    const isLoading = boolean("Is loading", false);
    const hasIcon = boolean("Has icon", true);
    const isIconBefore = boolean("Is icon before", true);
    const icon = hasIcon ? <EditIcon width={18} height={18} /> : undefined;

    const content = (
        <Button
            text={placeholder}
            appearance={appearance}
            icon={icon}
            isIconBefore={isIconBefore}
            isDisabled={isDisabled}
            isLoading={isLoading}
        />
    );
    return <div>{content}</div>;
});

We don't need to add a decorator here for knobs because we already set this as default in our config.js file:

addDecorator(withKnobs);

However, if we want to use the JSX addon, we will need to add a decorator in our story called addWithJSX:

storiesOf("Button", module).addWithJSX("story", () => <p>Lorem</p>))

Run Storybook

We can add a command in our package.json:

"storybook": "start-storybook -p 9001 -c .storybook"

This will open a new browser tab in the port 9001 with the .storybook configuration.

See more about Storybook CLI commands

Last updated