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.

Categories
Storybook for React

Storybook for React — Writing and Browsing Stories

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to write and browse stories with Storybook.

Browse Stories

We can browse stories by running:

npm run storybook

Then we’ll see the storybook at the URL that’s displayed.

The sidebar has the components.

And we have the preset props that are set with args on the left side.

We can zoom in visually and change the props with that.

The background can be changed so that we can see what the component looks like with different backgrounds.

We can also change the viewport side so we can see how it renders with them.

The docs tab shows the documentation about the components.

They are inferred from the source code.

Toolbars are customizable. We can use globals to toggle themes and languages.

Also, we can add addons to extend Storybook’s functionality.

They are located at the bottom of the preview pane.

The Controls addon lets us interact with the component.

Actions let us set the callbacks and simulate the result that we get with them.

Write Stories

We can write stories by adding some code with Storybook.

We put our story code files in the src/stories folder.

The story files end with the stories.js or stories.ts file.

We can set the story name with the storyName property.

For example, we can write:

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.storyName='primary button';

to create a basic story.

We set the story name by assigning a string to the storyName property.

Also, instead of using args, we can set our own props with a story.

For example, we can write:

export const Primary = () => <Button primary label="Button" />;
Primary.storyName='primary button';

to add the Primary story.

Also, we can set various parameters that we can test our component with the parameters property.

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', },
        { name: 'blue', value: '#00f', },
      ]
    }
  }
};

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 added the backgrounds property which should be displayed at the top menu.

This way, we can set different background colors and see what our component looks like with it.

Conclusion

We can add components with their stories and test them with Storybook.

Categories
Storybook for React

Storybook for React — Decorators and Multiple Components

Storybook lets us prototype components easily with various parameters.

In this article, we’ll look at how to write and browse stories with Storybook.

Decorators

We can add decorators with the decorators property.

It’s used to wrap our component with our own markup.

For example, we can write:

src/stories/Button.js

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,
  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,
};

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',
};

to add decorators with the decorators property.

It’s an array with functions we render the component with.

Story is the component that we’re wrapping the component with.

It should be the button since we’re testing the button.

Stories for two or more components

If we have 2 or more components, then we put them under the same folder.

For example, we can write:

src/stories/ListItem.js

import React from 'react';

export const ListItem = ({ text }) => {
  return (
    <li>
      {text}
    </li>
  );
};

src/stories/List.js

import React from 'react';
import PropTypes from 'prop-types';

export const List = ({ children, backgroundColor }) => {
  return (
    <ul style={{ backgroundColor }}>
      {children}
    </ul>
  );
};

List.propTypes = {
  backgroundColor: PropTypes.string
}

src/stories/List.stories.js

import React from 'react';
import { List } from './List';
import { ListItem } from './ListItem';

export default {
  component: List,
  title: 'List',
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

export const Empty = (args) => <List {...args} />;

export const OneItem = (args) => (
  <List {...args}>
    <ListItem text='foo' />
  </List>
);

export const ManyItems = (args) => (
  <List {...args}>
    <ListItem text='foo' />
    <ListItem text='bar' />
    <ListItem text='baz' />
  </List>
);

We created the ListItem and List components that we used together in our story.

The List component accepts the backgroundColor prop and we set the argTypes property to set the control for it to a color picker.

This way, we can set the color for it.

Conclusion

We can compose different components and test them together within one story with Storybook.

Categories
Storybook for React

Storybook for React — Args

Storybook lets us prototype components easily with various parameters.

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

Args Object

The args object lets us pass in arguments that we can pass into our components to change it.

It can be passed at the story and component level.

Args is an object with string keys.

Story Args

We can add args to a story by writing:

src/stories/button.css

.button {
  font-weight: 700;
  border: 0;
  border-radius: 3em;
  cursor: pointer;
  display: inline-block;
  line-height: 1;
}
.button-primary {
  color: white;
  background-color: #1ea7fd;
}
.button-secondary {
  color: #333;
  background-color: transparent;
}
.button-small {
  font-size: 12px;
  padding: 10px;
}
.button-medium {
  font-size: 14px;
  padding: 11px;
}
.button-large {
  font-size: 16px;
  padding: 12px;
}

src/stories/Button.js

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,
  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,
};

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 create the Template function so that we render the Button with the props we want.

Then we call Template.bind so that we can set the args property on the returned object.

This way, we can set the default value of the props.

We can merge different args object together.

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 PrimaryLongName = Template.bind({});

PrimaryLongName.args = {
  ...Primary.args,
  label: 'Primary button long name',
}

We merged the args from the Primary object into the PrimaryLongName object so we can reuse it.

Component Args

We can define args on a component.

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' },
  },
  args: {
    primary: true,
  },
};

We set the primary prop to true on all the buttons in the story.

Conclusion

We can set arguments in our stories to pass in different props to our React components for testing.