👩‍🎨

🎨Storybook@7入門:Next.js + TailwindCSSへのステップバイステップ組み込みガイド

2023/05/22に公開

Storybookのメジャーバージョンが7にアップデートしてからしばらく経ちます。

しかしながらNext.jsとtailwindCSSの組み合わせにStorybookを使用する記事が少なかったため、
組み込むために行ったことをまとめました。

https://storybook.js.org/docs/react/get-started/install/

動作確認環境は以下のとおりです。

"next": "13.3",
"react": "^18.2.0",
"tailwindcss": "^3.3.2",
"storybook": "^7.0.11",
"@storybook/addon-essentials": "^7.0.11",
"@storybook/addon-interactions": "^7.0.11",
"@storybook/addon-links": "^7.0.11",
"@storybook/blocks": "^7.0.11",
"@storybook/nextjs": "^7.0.11",
"@storybook/react": "^7.0.11",
"@storybook/testing-library": "^0.1.0",
"storybook-addon-pseudo-states": "^2.0.1",

インストール手順

公式の通りにイニシャライズします。

npx storybook@latest init

package.jsonに追加されるのを確認します。
(不足していたら別途インストールしてください)

package.json
"devDependencies": {
    "@storybook/addon-essentials": "^7.0.11",
    "@storybook/addon-interactions": "^7.0.11",
    "@storybook/addon-links": "^7.0.11",
    "@storybook/blocks": "^7.0.11",
    "@storybook/nextjs": "^7.0.11",
    "@storybook/react": "^7.0.11",
    "@storybook/testing-library": "^0.1.0",
    "eslint-plugin-storybook": "^0.6.12",
    "storybook": "^7.0.11",
},

.eslintrc.jsonにも追記します。

.eslintrc.json
"extends": [
    "prettier",
    "plugin:storybook/recommended"
  ],

Storybookはバージョンが7になったことで、6では手動で入れていたアドオンが集約されました。
tailwindを導入している場合は以下のように記述するだけでStorybook側に認識されます。

/src/styles/tailwind.css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
.storybook/preview.ts
import type { Preview } from '@storybook/react';
import '../src/styles/tailwind.css';

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
};

export default preview;

.storybook/main.ts
import type { StorybookConfig } from '@storybook/nextjs';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/nextjs',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
};
export default config;

各コンポーネントと対になるStorybookを追加する

例として、Buttonコンポーネントを取り上げます。
最終的な実装は以下のとおりです。

/src/components/Button/index.tsx
import React, { ButtonHTMLAttributes } from 'react';
import classNames from "classnames"

type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement>;

export const Button: React.FC<ButtonProps> = ({ children, innerClassName, ...rest }) => {
  return (
    <button
      className={classNames([innerClassName, "bg-custom-blue rounded-md bg-blue-500 px-4 py-2 text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"])}
      {...rest}
    >
      {children}
    </button>
  );
};

これと対になるStorybookファイルは以下のように実装しました。

/src/components/Button/index.stories.tsx
import { Meta, StoryObj } from '@storybook/react';
import { Button } from './index';

const meta = {
  title: 'MySite/Buttons(ボタン)',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    innerClassName: {
      control: {
        type: 'text',
      },
    },
  },
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof Button>;

export const _Button: Story = {
  render: (arg) => <Button {...arg}>Button</Button>,
};

Storybookの実装

この節では、細かい実装部分を取り上げます。

Storybook@7ではメタ情報をmeta変数にまとめ、default exportするようになりました。

const meta = {
  title: 'MySite/Buttons(ボタン)',
  component: Button,
  tags: ['autodocs'],
} satisfies Meta<typeof Button>;

export default meta;

このうち、titleプロパティに指定するのがStorybookに表示されるコンポーネントのグループ名になります。
「/」で区切ることでディレクトリのように階層構造を表現できます。

また、tags配列に挿入しているautodocsは、公式ドキュメントによると、

const meta: Meta<typeof Button> = {
  title: 'Button',
  component: Button,
  //👇 Enables auto-generated documentation for the component story
  tags: ['autodocs'],
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

とあるので、ブラウザで確認する際の自動生成のために必要であることがわかります。

ちなみに、export const _Buttonとすると、
Storybook上のコンポーネント名はアンダースコアを取り除いた状態(_Buttonの場合はButton)で表示されます。

PropsをStorybook側から受け渡す

meta変数のargTypesプロパティを追加し、propsinnerClassNameを登録し、Storybook上から入力できるようにします。

const meta = {
  title: 'MySite/Buttons(ボタン)',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    innerClassName: {
      control: {
        type: 'text',
      },
    },
  },
} satisfies Meta<typeof Button>;

また、同ファイル内で宣言している_Buttonオブジェクトにて、renderメソッドを介して引数を受け取ることが出来ます。

export const _Button: Story = {
  render: (arg) => <Button {...arg}>Button</Button>,
};

Storybook上ではテキストを入力できるよう表示されるようになりました。

ロゴやサービス名を表示させる

Storybookをブラウザで表示させると、デフォルトではStorybookのロゴが表示されます。

このままでも問題ないですが、自社のロゴやサービス名を表示させることで、より自分たちのデザインシステムとして感じることができ、
気持ちの面でもよい効果をもたらしますので変更することをオススメします!

この機能は@storybook/addon-essentialsが受け持ちますが、これまでの手順の中ですでにインストールされています。
.storybookディレクトリ に manager.js を追加し、以下のように記述することで変更することで実装できます。

.storybook/manager.js
import { addons } from '@storybook/addons';
import { create } from '@storybook/theming';

addons.setConfig({
  theme: create({
    base: 'light', // or 'dark'
    brandTitle: 'My Service',
    brandImage: '<IMAGE_PATH>'
  }),
});

他にも設定することができる項目があるので、公式ドキュメントをご確認ください。
https://storybook.js.org/docs/react/configure/theming#create-a-theme-quickstart

まとめ

Next.jsとtailwindCSSを利用したプロジェクトにStorybookの実装する方法を見てきました。
ぜひ取り入れてみてください!


ちょっと社では絶賛エンジニアを募集しています!
Jamstackに興味があるエンジニアさんはぜひご連絡ください〜〜
一緒に働きましょう〜〜🙌

ちょっと株式会社
https://chot-inc.com/

chot Inc. tech blog

Discussion