ReactプロジェクトにStorybookを導入する

Reactのライブラリを開発しているときに立ちはだかるのが、実際に開発中のものをインストールして動かしたりしないと確認できないということです。

ここでStorybookという、Previewにもってこいのライブラリがあったので、導入していきたいと思います。

あと、このStorybookはGithubPagesに公開することで、Repositoryから簡単にテストしてもらうことができるようになります。

Storybookの導入

とりあえず、プロジェクトにStorybookを導入します。

$ npx storybook init
Need to install the following packages:
  storybook
Ok to proceed? (y) y

npm8を使ってるプロジェクトだと、以下のような警告が出てくる場合があります。

We've detected you are running npm 8.11.0
 which has peer dependency semantics which Storybook is incompatible with.

In order to work with Storybook's package structure, you'll need to run `npm` with the
`--legacy-peer-deps=true` flag. We can generate an `.npmrc` which will do that automatically.

More info: https://github.com/storybookjs/storybook/issues/18298

Do you want to run the 'npm7' migration on your project?

どうもnpm8だと、依存関係の問題で正常に動作しないらしく、npm7のマイグレーションを行うことで使えるようになるようです。

特に問題がない人は、マイグレーションを実行します。そうすると .npmrc というファイルが作成されます。

.npmrcとはなんぞや

npmを実行するときの設定値を書いておくためのファイルです。

.eslintrc.js, prettierrc.js とかと同じ感じですね。

docs.npmjs.com

インストールが終わると以下のようになるので npm run storybook を実行します。

✔ Do you want to run the 'npm7' migration on your project? … yes
✅ ran npm7 migration

✅ migration check successfully ran


To run your Storybook, type:

   npm run storybook 

For more information visit: https://storybook.js.org

storybookを起動すると、ブラウザが自動的に立ち上がって色々と作業ができるようになります。

Storyboardの書き方

インストールしたてのStoryboard画面。ここにいくつかカスタマイズと、実際にコンポーネントを表示できるようにしていきます。

cssを適用する

現状のままだと、Componentが左上に寄って表示されてしまうので、センタリングされるようにカスタムcssを適用できるようにします。

まずは.storybook/assets/cssstyles.cssを作成して、以下をコピペします。

.preview-container {
  display: flex;
  justify-content: center;
  align-content: center;
  height: stretch;
}

.content-container {
  align-self: center;
  padding: 10px;
}

.content-container::-webkit-scrollbar {
  display: none;
}

次に styles.css を読み込めるように .storybook/preview.js に以下の項目を追加します。

+ import "./assets/css/styles.css";
+
  export const parameters = {
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  }

CLIでstorybookを起動している場合は再起動してから、ブラウザのDevTools等で確認すると、読み込まれていることが分かります。

background-colorを変更できるようにする

初期値では light, dark の2つしか選択できないので、もう少し選択できるように種類を増やします。

.storybook/backgrounds.jsというファイルを作成し、以下の内容をコピペします。

もっと色を増やしたい場合には、同じようなフォーマットでパターンを追加します。

const backgrounds = {
  default: 'White',
  values: [
    {name: 'White', value: '#FFFFFF', default: true},
    {name: 'Whitesmoke', value: 'whitesmoke'},
    {name: 'Black', value: 'black'},
    {name: 'Red', value: '#EF9A9A'},
    {name: 'Pink', value: '#F48FB1'},
    {name: 'Purple', value: '#CE93D8'},
    {name: 'Deep Purple', value: '#B39DDB'},
    {name: 'Indigo', value: '#9FA8DA'},
    {name: 'Blue', value: '#90CAF9'},
    {name: 'Light Blue', value: '#81D4FA'},
    {name: 'Dark Blue', value: '#5CB1EC'},
    {name: 'Green', value: '#A5D6A7'},
    {name: 'Deep Orange', value: '#FFAB91'},
    {name: 'Blue Grey', value: '#B0BEC5'}
  ]
};

export default backgrounds;

preview画面で設定できるように、.storybook/preview.jsに以下の行を追加します。

  import "./assets/css/styles.css";
+ import backgrounds from "./backgrounds";

  export const parameters = {
    actions: { argTypesRegex: "^on[A-Z].*" },
+   backgrounds,
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  }

設定が終わると、設定できるbackgroundColorが増えていると思います。

storiesファイルを作る

storybookをインストールすると stories というフォルダが作成されますが、

個人的には、componentsと同じ箇所にある方が、メンテナンスしやすいかなと思いそうしていますが、ここは各自の方針で実装してください。

細かい書き方については、実際にstorybookを導入しているライブラリがありますので、確認してみてください。

github.com

まずは、storybookに表示するメタ情報をexport defaultで定義します。

export default {
  title: "Timeline Embed Components",
  component: TwitterTimelineEmbed,
  argTypes: {
    backgroundColor: {
      control: "color"
    }
  }
} as ComponentMeta<typeof TimelineEmbed>;

次に、Previewしたいコンポーネントのベース定義をします。

ここでのargsとは、このテンプレートを継承した各パターン定義の際に設定するargsが代入されてきます。

const Template: Story<TimelineEmbedProps> = (args) => (
  <div className="preview-container">
    <div className="content-container">
      <TimelineEmbed {...args} />
    </div>
  </div>
);

定義したテンプレートを継承した、各プレビューパターンを定義します。

export const TimelineFromScreenName = Template.bind({});
TimelineFromScreenName.args = {
  sourceType: "profile",
  screenName: "nomunomu0504",
  options: {
    width: 400,
  },
};

export const TimelineFromUserId = Template.bind({});
TimelineFromUserId.args = {
  sourceType: "profile",
  userId: "740893939977195520",
  options: {
    width: 400,
  },
};

最終的には、こんな感じになります。

import { Story, ComponentMeta } from "@storybook/react";
import { TimelineEmbed } from "../TimelineEmbed";
import { TimelineEmbedProps } from "../types/TimelineEmbed";

export default {
  title: "Timeline Embed Components",
  component: TimelineEmbed,
  argTypes: {
    backgroundColor: {
      control: "color",
    },
  },
} as ComponentMeta<typeof TimelineEmbed>;

const Template: Story<TimelineEmbedProps> = (args) => (
  <div className="preview-container">
    <div className="content-container">
      <TimelineEmbed {...args} />
    </div>
  </div>
);

export const TimelineFromScreenName = Template.bind({});
TimelineFromScreenName.args = {
  sourceType: "profile",
  screenName: "nomunomu0504",
  options: {
    width: 400,
  },
};

export const TimelineFromUserId = Template.bind({});
TimelineFromUserId.args = {
  sourceType: "profile",
  userId: "740893939977195520",
  options: {
    width: 400,
  },
};

ここまで実装して、storybook-cliを再起動すると、ブラウザでコンポーネントが確認できるようになっています。

コンポーネントへ受け渡すための引数に対して、JSDoc等でコメントを記載しておくと、Docsタブを開いた際に自動的に説明文として設定することができます。

export type TimelineProfileDataSource =
  | {
      sourceType: "profile";

      /**
       * Valid Twitter username
       */
      screenName: string;

      /**
       * Additional options
       */
      options?: CommonOption & TimelineEmbedOption;
    }
  | {
      sourceType: "profile";

      /**
       * Valid Twitter user ID
       */
      userId: stirng;

      /**
       * Additional options
       */
      options?: CommonOption & TimelineEmbedOption;
    };

このstoriesファイルを、全てのコンポーネントに対して作成することで、手軽にコンポーネントをテストすることができるようになります。