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.

Categories
Storybook for React

Storybook for React — Decorators

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to work with decorators with Storybook.

Decorators

A decorator is a way to wrap stories with extra rendering functionality.

Wrap Stories with Extra Markup

We can wrap stories with extra markup with decorators.

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' },
  },
  decorators: [(Story) => <div style={{ margin: '20px' }}><Story /></div>]
};

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 decorators property with an array of functions that renders components around our component.

Story is the component that we’re rendering in our story.

Context for Mocking

We can wrap our component with a higher-order component globally.

For example, we can write:

.storybook/preview.js

import { ThemeProvider } from 'styled-components';

export const decorators = [
  (Story) => (
    <ThemeProvider theme="default">
      <Story />
    </ThemeProvider>
  ),
];

to wrap the ThemeProvider HOC from styled-components around the components in our stories.

Story Decorators

We can add decorators that are available within a story.

To add it, we just add the decorators property to our story object:

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',
};
Primary.decorators = [(Story) => <div style={{ margin: '20px' }}><Story /></div>]

to assign the decorators array to the decorators property of the Primary story.

Component Decorators

We can set decorators on all stories of a component.

To do that, we put the decorators property in the object that we export:

src/stories/Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
  decorators: [(Story) => <div style={{ margin: '20px' }}><Story /></div>]
};

Global Decorators

We can add global decorators in the .storybook/preview.js file by exporting it:

import React from 'react';

export const decorators = [(Story) => <div style={{ margin: '20px' }}><Story /></div>]

We export the decorators array to apply it everywhere.

Decorator Inheritance

Decorators are applied in a specific order.

Global ones are applied in the order they’re defined first.

Then component decorators are applied in the order they’re defined.

And story decorators are applied in the order they’re defined.

Conclusion

Decorators let us add extra markup to our component and render them in Storybook.

Categories
Storybook for React

Storybook for React — Args Composition and Parameters

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to work with args and parameters with Storybook.

Args Composition

We can combine different args together to create stories.

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 PrimaryLarge = Template.bind({});
PrimaryLarge.args = {
  ...Primary.args,
  ...Large.args,
}

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

to create the PrimaryLarge arg, which is created by combining the Primary and Large args into one.

Parameters

Parameters are a set of static, named metadata about a story.

It’s used to control the behavior of Storybook features and addons.

For example, we can control the background color with our own parameters.

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',
};
Primary.parameters = {
  backgrounds: {
    values: [
      { name: 'red', value: '#f00' },
      { name: 'green', value: '#0f0' },
    ],
  },
};

to add the parameters we can set in our story.

With the backgrounds property, we should see a menu on the top of the screen to let us set the background color in the preview window.

values has the name and value of the choices we can choose.

Now if we click in the Primary story, we should be able to select red or green from the menu to set the background color.

Component Parameters

Also, we can add parameters component-wide.

For example, we can write:

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
  parameters: {
    backgrounds: {
      values: [
        { name: 'red', value: '#f00' },
        { name: 'green', value: '#0f0' },
      ],
    }
  }
};

to add the backgrounds menu to all stories.

Global Parameters

Parameters can also be set globally.

To do that, we add them to the .storybook/preview.js file:

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  backgrounds: {
    values: [
      { name: 'red', value: '#f00' },
      { name: 'green', value: '#0f0' },
    ],
  },
}

We add the backgrounds menu to our parameters object that we exported.

Parameter Inheritance

When there are global, component, and story parameters, then they’re combined with the more specific ones taking precedence over the less specific ones.

So the story ones take precedence over the component ones.

And the component ones take precedence over the global ones.

Conclusion

We can compose args and add parameters to set various things to let us preview our component in Storybook.