みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Next.js(TypeScript + CSSModules)にStorybookを導入する 📕

前回のプロジェクトStorybookを導入します。

blog.kimizuka.org
storybook.js.org

以前、Next.js + styled-componentsの構成にStorybookを入れたときはnpxを使っていましたが、今回はyarnを使います。

blog.kimizuka.org

導入手順

❶ Storybookのセットアップ

公式のドキュメントにNext.jsへの導入方法が書いてあるので、そのままです。

storybook.js.org

yarn create storybook

で、OKです。

フルバージョンを入れるか、ミニマルバージョンを入れるかの選択を迫られるのですが、僕はミニマルバージョンにしました。
セットアップが終わると、 http://localhost:6006/?path=/story/example-button--primary が立ち上がるのですが、落としてしまって大丈夫です。

これだけで、もろもろ設定が完了します。
例えば、package.jsonに、

"scripts": {
  ...
  "storybook": "storybook dev -p 6006",
  "build-storybook": "storybook build"
},

が追加されるので、

yarn storybook

で、Storybookが起動できるようになってます。
ただ、余計なお世話といえるものも作られていて、例えば src/stories 以下に色々作成されるのですが、僕はまるっと削除してます。

❷ globals.css読み込み

.storybook/preview.ts
import '@/app/globals.css'; // 追加
import type { Preview } from '@storybook/nextjs';

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

export default preview;

.storybook/preview.tsにてglobals.cssを読み込みます。

以上です。
src/stories 以下をまるっと削除すると、

yarn storybook

でブラウザを起動しても表示するコンポーネントがなくなってしまうので、
ついでにsrc/stories/Button.tsxをcomponents/Button/index.tsxに移植してみます。

src/components/Button/index.tsx

import styles from './index.module.css';

export interface ButtonProps {
  primary?: boolean;
  backgroundColor?: string;
  size?: 'small' | 'medium' | 'large';
  label: string;
  onClick?: () => void;
}

export const Button = ({
  primary = false,
  size = 'medium',
  backgroundColor,
  label,
  ...props
}: ButtonProps) => {
  const buttonSize = (() => {
    switch (size) {
      case 'small':
        return styles.buttonSmall;
      case 'large':
        return styles.buttonLarge;
      default:
        return styles.buttonMedium;
    }
  })();
  const buttonMode = primary ? styles.buttonPrimary : styles.buttonSecondary;

  return (
    <button
      type="button"
      className={[styles.button, buttonSize, buttonMode].join(' ')}
      {...props}
    >
      {label}
      <style jsx>{`
        button {
          background-color: ${backgroundColor};
        }
      `}</style>
    </button>
  );
};

src/components/Button/index.module.css

.button {
  display: inline-block;
  cursor: pointer;
  border: 0;
  border-radius: 3em;
  font-weight: 700;
  line-height: 1;
  font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.buttonPrimary {
  background-color: #555ab9;
  color: white;
}

.buttonSecondary {
  border: solid 1px white;
  box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
  background-color: transparent;
  color: white;
}

.buttonSmall {
  padding: 10px 16px;
  font-size: 12px;
}

.buttonMedium {
  padding: 11px 20px;
  font-size: 14px;
}

.buttonLarge {
  padding: 12px 24px;
  font-size: 16px;
}

src/components/Button/index.stories.ts

import type { Meta, StoryObj } from '@storybook/nextjs';

import { fn } from 'storybook/test';

import { Button } from './index';

// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
  title: 'Example/Button',
  component: Button,
  parameters: {
    // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
    layout: 'centered',
  },
  // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
  tags: ['autodocs'],
  // More on argTypes: https://storybook.js.org/docs/api/argtypes
  argTypes: {
    backgroundColor: { control: 'color' },
  },
  // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
  args: { onClick: fn() },
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary: Story = {
  args: {
    label: 'Button',
  },
};

export const Large: Story = {
  args: {
    size: 'large',
    label: 'Button',
  },
};

export const Small: Story = {
  args: {
    size: 'small',
    label: 'Button',
  },
};

これで、ボタンコンポーネントが表示されるようになります。

3回に分けましたが、これでNext.js(TypeScript + CSSModules)+ biome + happy-css-modules + StorybookをVSCodeで開発する体制が整いました。
しばらくはこの体制で開発していこうと思います。

biome導入方法

blog.kimizuka.org

happy-css-modules導入方法

blog.kimizuka.org