Categories
Storybook for React

Storybook for React — Working with Documentation

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to add descriptions to our documentation, and adjust them, preview, and bud them with Storybook.

Description

We can add a description for a component with Storybook.

The description can be extracted from the source code or we can provide our own description.

For example, we can write:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    docs: {
      description: {
        component: '**button** component'
      }
    },
  }
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

We have the parameters.docs.description.component property which accepts a Markdown string for our description.

Preview and Build Docs

We can preview Storybook’s documentation with the start-storybook --docs command.

To make our lives easier, we can add the command to the scripts section of our React project’s package.json .

For example, we can write:

{
  "scripts": {
    "storybook-docs": "start-storybook --docs",
  }
}

to add it to package.json .

Publish Storybook’s Documentation

We can publish Storybook’s documentation with the build-storybook --docs .

Likewise, we can put it into package.json ‘s scripts property:

{
  "scripts": {
    "build-storybook-docs": "build-storybook --docs",
  }
}

The output will be in the storybook-static folder.

Controls

We can set the controls for our args so we can change their values and preview the results.

To do that, we add the argTypes property to the object that we’re exporting in our stories file.

For example, we can write:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

We have:

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

to let us set the value of the backgroundColor prop with a color picker.

The control property lets us set the control type for setting the prop.

Custom Args

We can create our own custom args

To do this, we can set the type and options properties to add a radio button control with our own options:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    size: {
      control: {
        type: 'inline-radio',
        options: ['small', 'medium', 'large'],
      },
    },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

We set the type to 'inline-radio' to see a series of radio buttons

options has the label and value for each radio button.

Show Full Documentation for Each Property

We can make Storybook show the full documentation for each property.

For example, we can write the following in .storybook/preview.js to expand all the documentation:

export const parameters = {
  controls: { expanded: true },
}

The controls.expanded property set to true lets us show the expanded documentation for each property.

Conclusion

We can add descriptions, change what’s displayed, and preview and build it with Storybook.

Categories
Storybook for React

Storybook for React — MDX Documentation

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to add MDX documentation with Storybook.

MDX

The MDX file format mixes Markdown with JSX.

We can use Markdown’s succinct syntax for our documentation.

Storybook will compile the Markdown and JSX together into a document.

We create a MDX file with the stories.mdx to create our documentation.

For example, we can write:

Button.stories.mdx

import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks';
import { Button } from './Button';

<Meta title="Example/Button" component={Button} />

# Button

A button

<!--- This is your Story template function, shown here in React -->

export const Template = (args) => <Button {...args} />

<Canvas>
  <Story name="Primary" args={{
      label: 'button',
      primary: true
    }}>
    {Template.bind({})}
   </Story>
  <Story name="Secondary" args={{
      label: 'button',
    }}>
    {Template.bind({})}
   </Story>
  <Story name="Secondary" args={{
    label: 'button'
  }}>
    {Template.bind({})}
   </Story>
</Canvas>

Then in the Storybook screen, we see the button displayed.

The Canvas component is the container for the documentation.

Story has the stories we populate in our documentation.

The Markdown description will show in the Docs tab.

Decorators and Parameters

We can add decorators and parameters in the MDX document.

We can add them as the props of the Meta component.

For example, we can add the decorators prop by writing:

import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks';
import { Button } from './Button';

<Meta
  title="Example/Button"
  component={Button}
  decorators={[(Story) => <div style={{ margin: '20px' }}><Story/></div>]}
/>

# Button

A button

<!--- This is your Story template function, shown here in React -->

export const Template = (args) => <Button {...args} />

<Canvas>
  <Story name="Primary" args={{
      label: 'button',
      primary: true
    }}>
    {Template.bind({})}
   </Story>
  <Story name="Secondary" args={{
      label: 'button',
    }}>
    {Template.bind({})}
   </Story>
  <Story name="Secondary" args={{
    label: 'button'
  }}>
    {Template.bind({})}
   </Story>
</Canvas>

To add parameters, we can write:

import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks';
import { Button } from './Button';

<Meta
  title="Example/Button"
  component={Button}
  parameters={{
    backgrounds: {
      values: [
        { name: 'red', value: '#f00' },
        { name: 'green', value: '#0f0' },
      ],
    },
  }}
/>

# Button

A button

<!--- This is your Story template function, shown here in React -->

export const Template = (args) => <Button {...args} />

<Canvas>
  <Story name="Primary" args={{
      label: 'button',
      primary: true
    }}>
    {Template.bind({})}
   </Story>
  <Story name="Secondary" args={{
      label: 'button',
    }}>
    {Template.bind({})}
   </Story>
  <Story name="Secondary" args={{
    label: 'button'
  }}>
    {Template.bind({})}
   </Story>
</Canvas>

We added the parameters prop with the usual parameters object.

Now we should see the background color dropdown which is added by adding the background property.

Documentation-only MDX

To define documentation-only MDX, we just add the Meta component but don’t define any stories.

For example, we can create a src/stories/Foo.stories.mdx file and add:

import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks';

<Meta
  title="Example/Foo"
/>

# Foo

foo bar

Now we should see a Foo link on the left sidebar and we can click on it to see the Markdown displayed as HTML.

Conclusion

We can add stories with MDX files, which mixes Markdown with JSX.

Categories
Storybook for React

Storybook for React — ArgsTable

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to add ArgsTable with Storybook.

Doc Blocks

Doc blocks are building blocks of Storybook documentation pages.

DocsPage uses a combination of doc block to form a document.

ArgsTable

Storybook docs automatically generate a component args table for components.

The tables list the arguments of the component.

It can also integrate with controls to let us change the args of the currently rendered story.

This can expanded with our own code by adding comments to document it.

For example, in our Button.js file, we can write:

import React from 'react';
import PropTypes from 'prop-types';
import './button.css';

export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
  const mode = primary ? 'button-primary' : 'button-secondary';
  return (
    <button
      type="button"
      className={['button', `button-${size}`, mode].join(' ')}
      style={backgroundColor && { backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};

Button.propTypes = {
  primary: PropTypes.bool,
  /**
    String for the background color
  */
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};

Button.defaultProps = {
  backgroundColor: null,
  primary: false,
  size: 'medium',
  onClick: undefined,
};

to create our Button component.

We have a comment above the backgroundColor prop.

This will be displayed in the Description column of the args table.

DocsPage

To add a DocsPage for a component, we just have to export an object with the components property for the file.

For example, we can write:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

We have the:

export default {
  title: 'Example/Button',
  component: Button
};

default export to export an object with the component property to make the button display the Button component.

Customizing ArgsTable

We can customize our ArgsTable by writing:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    primary: {
      description: 'overwritten description',
      table: {
        type: {
          summary: 'summary',
          detail: 'some detail'
        },
      },
      control: {
        type: null,
      },
    },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

We have the argsTypes property with an object that has properties for the primary argument.

The description property has the description.

table has an object that will be populated in the description.

summary is displayed as button that can be toggled to display the detail .

control has the control for setting the args.

We can change the source snippet that’s displayed for a story.

To do that, we can set the docs.source.code property:

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    primary: {
      description: 'overwritten description',
      table: {
        type: {
          summary: 'summary',
          detail: 'some detail'
        },
      },
      control: {
        type: null,
      },
    },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

Primary.parameters = {
  docs: {
    source: {
      code: 'Some custom string here'
    }
  },
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

The ‘Some custom string here’ string will be displayed when we click the Show code button.

Conclusion

We can add code to show different things in the ArgsTable to document the arguments that the component accepts in Storybook.

Categories
Storybook for React

Storybook for React — Naming and Hierarchy

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to name stories with Storybook.

Naming Components and Hierarchy

The title property is in the object we export as a default export in the story.

For example, we have:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

The title is at the object at the top.

We put them into the Example group with Example/ .

A forward slash separates the group into a tree of groups.

This will be displayed as a treeview on the left side of the Storybook screen so we can expand and collapse the groups.

Roots

The default group is the root group if we don’t have any group name specified.

Sorting Stories

We can sort stories by adding the storySort method the parameters object we export in the .storybook/preview.js :

import React from 'react';

export const parameters = {
  options: {
    storySort: (a, b) => a[0].localeCompare(b[0])
  },
};

We added the storySort method to our app with a comparator function to sort the stories alphabetically.

a[0] has the ID string of the story.

The storySort property can also accept an object literal:

.storybook/preview.js

import React from 'react';

export const parameters = {
  options: {
    storySort: {
      method: 'alphabetical',
      order: [],
      locales: 'en-US',
    }
  },
};

method is a string that tells Storybook how to sort the stories.

order is an array of story names that we can display in the order they appear in the array.

locales is a string with the locale used for sorting.

The order array can be nested to sort story groups.

For example, we can write:

import React from 'react';

export const parameters = {
  options: {
    storySort: {
      order: ['Introduction', 'Button'],
    }
  },
};

to sort the stories in the order they’re listed.

Document Components

We can document our components in various ways.

In the stories file, we export an object as the default export with the title and component properties.

The title is what we display on the screen.

And component is the component we’re previewing.

The props are automatically extracted from the component so that we can set them to our own values.

Subcomponents Parameter

We can document multiple components together.

For example, we can write:

src/stories/ButtonGroup.js

import React from 'react';

export const ButtonGroup = ({ children }) => {
  return <div>{children}</div>
}

src/stories/ButtonGroup.stories.js

import React from 'react';
import { Button } from './Button';
import { ButtonGroup } from './ButtonGroup';

export default {
  title: 'Example/ButtonGroup',
  component: ButtonGroup,
  subcomponents: { Button },
};

const Template = (args) => <ButtonGroup {...args} />;

export const Primary = Template.bind({});

We have the ButtonGroup component with a story.

The story file’s default export has the subcomponents property with the properties that are the child components of the ButtonGroup component.

Conclusion

We can document our components by naming them with Storybook.

Categories
Storybook for React

Storybook for React — Docs Page

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to replace the DocsPage template with Storybook.

Replacing DocsPage

We can replace the DocsPage template with our own content.

In the story level, we can write:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.parameters = { docs: { page: null } }

We set the docs.page property to null and then we’ll see the ‘No Docs’ page displayed.

Also, we can set the docs.page property at the component level.

To do this, we write:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    docs: { page: null }
  }
};

Then we’ll see the ‘No Docs’ message on all stories.

Global Level

We can set the docs.page property at the global level so we can replace the DocsPage in all stories.

For example, we can write:

./storybook/preview.js

export const parameters = {
  docs: { page: null }
}

Now all our stories will see the ‘No Docs’ message.

Remixing DocsPage

Also, we can add our own content by importing props from the page.

For example, we can write:

import React from 'react';
import { Button } from './Button';
import {
  Title,
  Subtitle,
  Description,
  Primary as PrimaryDoc,
  Props,
  Stories,
  PRIMARY_STORY,
} from '@storybook/addon-docs/blocks';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    docs: {
      page: () => (
        <>
          <Title />
          <Subtitle />
          <Description />
          <PrimaryDoc />
          <ArgsTable story={PRIMARY_STORY} />
          <Stories />
        </>
      ),
    },
  },
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

to add the components to builds our documentation page.

We install the @storybook/addon-docs package so that we can add a custom documentation page.

Title and Subtitle have the title and subtitle.

Description has the description of the component.

Primary has the preview.

ArgsTable has the arguments list.

Stories has the stories list.

PRIMARY_STORY has the main story of the Storybook.

Inline vs Iframe Stories

We can put stories inline or in an iframe.

To do that, we set inlineStories to true so that we can stop putting items in an iframe.

Also, we can write our own function and set it to the prepareForInline property to display docs inline.

For example, we can render Vue stories into React ones by writing:

import React from 'react';
import { render } from 'react-dom';
import toReact from '@egoist/vue-to-react';

export const parameters = {
  docs: {
    prepareForInline: (storyFn, { args }) => {
      const Story = toReact(storyFn());
      return <Story {...args} />;
    },
  },
}

We render the content returned by storyFn into a React component.

We need the @egoist/vue-to-react package to render the Vue content into a React component.

Conclusion

There’re many ways to render stories with our own content.

We can use the components provided by the Storybook addons.