Categories
Storybook for React

Storybook for React — Backgrounds and Globals

Storybook lets us prototype components easily with various parameters.

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

Backgrounds

We can set the backgrounds toolbar item to add choices for setting the background.

We can do this globally by adding the options in the .storybook/preview.js file:

export const parameters = {
  backgrounds: {
    default: 'twitter',
    values: [
      {
        name: 'twitter',
        value: '#00aced'
      },
      {
        name: 'facebook',
        value: '#3b5998'
      },
    ],
  }
}

We set the backgrounds property to set the choices for setting the background color.

values has an array of choices that we can set.

name is the name that’s displayed in the dropdown and value is the background color value.

default is the name of the choice to display.

We can also set the choices for one set of stories.

To do that, we 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' },
  },
  parameters: {
    backgrounds: {
      default: 'twitter',
      values: [
        {
          name: 'twitter',
          value: '#00aced'
        },
        {
          name: 'facebook',
          value: '#3b5998'
        },
      ],
    }
  }
};

We set the background color in the Button stories set within the parameters property.

Also, we can set the background color dropdown only for one story.

To do that, we 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: {
    default: 'twitter',
    values: [
      {
        name: 'twitter',
        value: '#00aced'
      },
      {
        name: 'facebook',
        value: '#3b5998'
      },
    ],
  }
};

The backgrounds can also be disabled with the disable property set to true :

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: { disable: true }
};

We disable the background dropdown with the Primary story.

Toolbars and Globals

We can create our own Storybook toolbar items to control special global variables.

Globals are non-story-specific inputs to the rendering of the story

They aren’t passed into a story as args.

We can add our own toolbar by putting some properties in the .storybook/preview.js file:

export const globalTypes = {
  theme: {
    name: 'Theme',
    description: 'Global theme for components',
    defaultValue: 'light',
    toolbar: {
      icon: 'circlehollow',
      items: ['light', 'dark'],
    },
  },
};

We have the theme property with some properties.

The toolbar property has the icon property to set the icon for the dropdown choices.

items has an array of items to choose from.

Create a Decorator

We can consume the theme global with a decorator.

For example, we can write:

.storybook/preview.js

import React from 'react';
import { ThemeProvider } from 'styled-components';

export const globalTypes = {
  theme: {
    name: 'Theme',
    description: 'Global theme for components',
    defaultValue: 'palevioletred',
    toolbar: {
      icon: 'circlehollow',
      items: ['palevioletred', 'white'],
    },
  },
};

const withThemeProvider = (Story, context) => {
  return (
    <ThemeProvider theme={{ main: context.globals.theme }}>
      <Story {...context} />
    </ThemeProvider>
  )
}
export const decorators = [withThemeProvider];

We added some theme choices with the toolbar property.

And we get the selected value with the context.globals.theme property.

We wrapped the ThemeProvider around our Story component, which is whatever component we display in the story.

And then we export the withThemeProvider we created which we put in an array.

Now we can set the theme by selecting it from the dropdown.

Conclusion

We can add choices for backgrounds and set globals which we use with decorators.

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.