Categories
Storybook for React

Storybook for React — Controls and argTypes

Storybook lets us prototype components easily with various parameters.

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

Controls

Storybook controls let us interact with the component’s arguments dynamically without changing the code.

They are convenient and portable.

For example, we can set a control for an arg with the argTypes property:

src/stores/Button.stories.js

import React from 'react';

import { Button } from './Button';

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

We set the backgroundColor to be set with a color picker with the control property set to 'color' .

Also, we can set a range slider with the type set to 'range' .

For example, we can write:

Button.js

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

export const Button = ({ primary, backgroundColor, borderRadius, size, label, ...props }) => {
  const mode = primary ? 'button-primary' : 'button-secondary';
  return (
    <button
      type="button"
      style={{ backgroundColor, borderRadius }}
      {...props}
    >
      {label}
    </button>
  );
};

Button.propTypes = {
  primary: PropTypes.bool,
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  borderRadius: PropTypes.number,
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};

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

Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    borderRadius: {
      control: { type: 'range', min: 0, max: 50, step: 1 },
    },
  },
};

We set the borderRadius prop to be changed with a range slide with the type set to 'range' .

Hide NoControls Warning

If we don’t plan to add any controls in our story, we can hide it with the hideNoControlWarning set to true .

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

Primary.parameters = {
  controls: { hideNoControlsWarning: true },
}

to hide the controls warning.

Actions

The actions addon lets us display data received by the event handler arguments for our stories.

We can set the argType to tell Storybook that an arg should be an action.

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: { onClick: { action: 'clicked' } },
};

We set the argTypes.onClick property to set the action to 'clicked' .

Then that’ll be displayed when we click the button.

Automatically Matching Args

We can also automatically match argTypes with a certain pattern.

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: { actions: { argTypesRegex: '^on.*' } },
};

We set the argTypesRegex property to match the event with the prop name.

Therefore, the click action will be matched with the onClick handler, so when we click the button, onClick will be run.

Conclusion

We can set the controls and the argType for event handlers with Storybook.

Categories
Storybook for React

Storybook for React — Globals and Addons and Stories

Storybook lets us prototype components easily with various parameters.

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

Consuming Globals within a Story

We can consume globals inside a story.

To do that, we get the globals property from the story function.

For example, we can write:

.storybook/preview.js

export const globalTypes = {
  locale: {
    name: 'Locale',
    description: 'locale',
    defaultValue: 'en',
    toolbar: {
      icon: 'globe',
      items: [
        { value: 'en', right: '??', title: 'English' },
        { value: 'fr', right: '??', title: 'Français' },
        { value: 'zh', right: '??', title: '中文' },
      ],
    },
  },
};

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 getCaptionForLocale = (locale) => {
  switch (locale) {
    case 'en': return 'Hello';
    case 'fr': return 'Bonjour';
    case 'zh': return '你好';
    default:
      return 'Hello'
  }
}

const Template = (args, { globals: { locale } }) => <Button {...{ ...args, label: getCaptionForLocale(locale) }} />;

We defined the locale global variable with the .storybook/preview.js file.

Then in the src/stories/Button.stories.js file, we get the locale global property with the globals.locale property of the parameter.

The right property is the text that’s displayed on the right side in the toolbar menu when we connect it to a decorator.

Consuming Globals within an Addon

We can consume globals within an addon file.

For example, we can write:

import React from 'react';
import { useGlobals } from '@storybook/api';
import { addons, types } from '@storybook/addons';
import { AddonPanel, Spaced, Title } from '@storybook/components';

const LocalePanel = props => {
  const [{ locale }] = useGlobals();

  return (
    <>
      <style>
        {`
        #panel-tab-content > div > div[hidden] {
          display: flex !important
        }
      `}
      </style>
      <AddonPanel {...props}>
        <Spaced row={3} outer={1}>
          <Title>{locale}</Title>
        </Spaced>
      </AddonPanel>
    </>
  );
};

const ADDON_ID = 'locale-panel';
const PANEL_ID = `${ADDON_ID}/panel`;

addons.register(ADDON_ID, (api) => {
  addons.add(PANEL_ID, {
    type: types.PANEL,
    title: 'Locale',
    render: ({ active, key }) => {
      return <AddonPanel active={active} key={key}>
        <LocalePanel />
      </AddonPanel>
    },
  });
});

to add an addon and get the global property with the useGlobal hook.

We use the AddonPanel , Spaced , and Title properties to show the content inside the addon panel.

The addons.register method register the addon so that it’ll be shown in the Storybook’s addon panel.

Also, we have the AddonPanel to render the panel.

We make it show when we display the panel with the style tag.

Story Rendering

We can change how our stories are rendered.

To add to the head tag, we add HTML code to the .storybook/preview-head.html file:

<link rel="stylesheet"
    href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
    integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
    crossorigin="anonymous">

Then they’ll be shown in the head tag inside the iframe.

Conclusion

We can add our own addons panel with Storybook.

Also, we can add tags inside the head tag in the iframe.

Categories
Storybook for React

Storybook for React — Controls and argTypes

Storybook lets us prototype components easily with various parameters.

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

Controls

Storybook controls let us interact with the component’s arguments dynamically without changing the code.

They are convenient and portable.

For example, we can set a control for an arg with the argTypes property:

src/stores/Button.stories.js

import React from 'react';

import { Button } from './Button';

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

We set the backgroundColor to be set with a color picker with the control property set to 'color' .

Also, we can set a range slider with the type set to 'range' .

For example, we can write:

Button.js

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

export const Button = ({ primary, backgroundColor, borderRadius, size, label, ...props }) => {
  const mode = primary ? 'button-primary' : 'button-secondary';
  return (
    <button
      type="button"
      style={{ backgroundColor, borderRadius }}
      {...props}
    >
      {label}
    </button>
  );
};

Button.propTypes = {
  primary: PropTypes.bool,
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  borderRadius: PropTypes.number,
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};

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

Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    borderRadius: {
      control: { type: 'range', min: 0, max: 50, step: 1 },
    },
  },
};

We set the borderRadius prop to be changed with a range slide with the type set to 'range' .

Hide NoControls Warning

If we don’t plan to add any controls in our story, we can hide it with the hideNoControlWarning set to true .

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

Primary.parameters = {
  controls: { hideNoControlsWarning: true },
}

to hide the controls warning.

Actions

The actions addon lets us display data received by the event handler arguments for our stories.

We can set the argType to tell Storybook that an arg should be an action.

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: { onClick: { action: 'clicked' } },
};

We set the argTypes.onClick property to set the action to 'clicked' .

Then that’ll be displayed when we click the button.

Automatically Matching Args

We can also automatically match argTypes with a certain pattern.

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: { actions: { argTypesRegex: '^on.*' } },
};

We set the argTypesRegex property to match the event with the prop name.

Therefore, the click action will be matched with the onClick handler, so when we click the button, onClick will be run.

Conclusion

We can set the controls and the argType for event handlers with Storybook.

Categories
Storybook for React

Storybook for React — Testing with Viewports and Actions

Storybook lets us prototype components easily with various parameters.

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

Action Event Handlers

We can specify event handlers that our button handles with the parameter.action.handles property.

To do that, we can add that property to the object we export in the story:

Button.stories.js

import React from 'react';

import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    actions: {
      handles: ['mouseover', 'click .btn']
    }
  }
};

We added an array of events to listen to.

Now when we move our mouse over the button or click it, we’ll see the event object displayed in Storybook’s console.

Viewport

Storybook gives us a set of common viewports to test with.

We can change the defaultViewport property in .storybook/preview.js so add the default viewport:

import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';

export const parameters = {
  viewport: {
    viewports: MINIMAL_VIEWPORTS,
    defaultViewport: 'someDefault',
  },
}

We import the MINIMAL_VIEWPORTS object to show a few viewports to we can choose from.

defaultViewport lets us set the default viewport name.

We can also add our own viewport sizes:

const viewports = {
  tablet: {
    name: 'tablet',
    styles: {
      width: '720px',
      height: '1280px',
    },
  },
  tablet2: {
    name: 'tablet 2',
    styles: {
      width: '1024px',
      height: '768px',
    },
  },
};

export const parameters = {
  viewport: {
    viewports,
    defaultViewport: 'tablet',
  },
}

We added a viewports object with the viewports we want to show on the dropdown menu.

Now we can set the sizes by choosing these sizes.

name has the names that are shown in the dropdown.

For example, we can write:

.storybook/preview.js

import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';

const viewports = {
  tablet: {
    name: 'tablet',
    styles: {
      width: '720px',
      height: '1280px',
    },
  },
  tablet2: {
    name: 'tablet 2',
    styles: {
      width: '1024px',
      height: '768px',
    },
  },
};

export const parameters = {
  viewport: {
    viewports: {
      ...MINIMAL_VIEWPORTS,
      ...viewports
    },
    defaultViewport: 'tablet',
  },
}

We merged the MINIMAL_VIEWPORTS and the viewports objects into one and set it as the value of the viewports property.

Now we see entries from both objects displayed in the dropdown.

Configuring Viewports Per Component or Story

We can also configure viewports per component or story.

To do that, we add the parameters.viewport property:

Button.stories.js

import React from 'react';
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    viewport: {
      viewports: MINIMAL_VIEWPORTS,
      defaultViewport: 'iphone6'
    },
  }
};

to set the viewports of the Button stories to the MINIMAL_VIEWPORTS object.

To set the viewport for a story, we can write:

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

Primary.parameters = {
  viewport: {
    defaultViewport: 'small mobile'
  },
};

Now we set the default viewport for the Primary story.

Conclusion

We can set the viewport choices for different stories with Storybook so we can test with various screen sizes.

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.