📗

【Next.js】Storybookにふれる(2) 

2024/09/21に公開

Storybookを使ったコンポーネントのイベント管理

前回のStorybook導入に引き続いて、今回はStorybookのActionを使ったコールバックのハンドリングやコンポーネントのカタログ管理に役立つドキュメント作成の機能についてふれてみたいと思います。

Storybookには前回触ったControlsタブのとなりにActionsタブというものがあります。このタブではコンポーネントクリック時のイベント管理を行うことができます。
現在の状態では、onClickイベントの実装を行なっていないので、もちろんStorybookのUI上に表示されているボタンをクリックしても何も起こりません。

ここでargTypeにonClickプロパティを追加してみます。
そうするとクリックに合わせてActionタブに情報が出力されました。

import { Meta, StoryFn } from '@storybook/react'
import { StyledButton, StyledButtonProps } from '@/components/StyledButton'

export default {
  title: 'StyledButton',
  component: StyledButton,
  argTypes: {
    variant: {
      control: { type: 'radio' },
      options: ['blue', 'green', 'transparent'],
    },
    children: {
      control: { type: 'text' },
    },
    onClick: { action: 'clicked' }
  },
} as Meta<typeof StyledButton>

const Template: StoryFn<StyledButtonProps> = (args) => <StyledButton {...args} />

export const TemplateTest = Template.bind({})

TemplateTest.args = {
  variant: 'blue',
  children: '青いボタン',
}

ここで出力されているのはReactのイベントシステムが生成するSynthetic Event(合成イベント)の情報です。ただし、このままでは内容が分かりにくいので、任意のデータをActionsタブに表示できるようにするために以下の手順に沿って設定を追加します。

  1. @storybook/addon-actionsからactions関数をインポートします。
  2. インポートしたactions関数に任意の文字列を渡すことでactions出力のための関数を定義します。ここで渡した文字列が、Actionsタブに出力される値のキーとなります。
  3. Storybookコンポーネントの中で先ほど定義した関数を呼び出します。
  4. actions出力用の関数の引数に渡した内容がそのままActionsタブの対応するキーの値として出力されます。

Templateコンポーネントにクリックイベントを追加してみましょう。なお、argTypeに追加したonClickプロパティは削除しておきます。

import { Meta, StoryFn } from '@storybook/react'
import { StyledButton, StyledButtonProps } from '@/components/StyledButton'
import { action } from '@storybook/addon-actions'

export default {
  title: 'StyledButton',
  component: StyledButton,
  argTypes: {
    variant: {
      control: { type: 'radio' },
      options: ['blue', 'green', 'transparent'],
    },
    children: {
      control: { type: 'text' },
    },
  },
} as Meta<typeof StyledButton>

// actions出力用の関数を定義
const customizedAction = action('customized')

const Template: StoryFn<StyledButtonProps> = (args) => {
  // clickイベントの定義。この関数の中で、actions出力用の関数を呼び出す
  const handleOnClick = () => {
    customizedAction('clicked') // <- この関数に渡した引数がActionsタブに表示される
  }
  return (
   {/* StorybookコンポーネントのonClickに定義したイベント関数を渡す */}
    <StyledButton {...args} onClick={handleOnClick} />
  )
}

export const TemplateTest = Template.bind({})

TemplateTest.args = {
  variant: 'blue',
  children: '青いボタン',
}

ボタンをクリックすると、以下のようにcustomizedAction関数の引数に渡した任意の値を出力することができました。

今回は出力関数customizedActionの引数に単独の文字列を渡して出力しましたが、引数を複数渡した場合は配列型式で値が出力されます。

出力関数にuseStateで状態管理している変数を渡すと、ボタンクリック時のその状態を出力させることもできます。以下はボタンクリックに合わせて1ずつ加算されていく変数countのクリック時の状態を都度出力させる例です。

import { Meta, StoryFn } from '@storybook/react'
import { StyledButton, StyledButtonProps } from '@/components/StyledButton'
import { action } from '@storybook/addon-actions'
import { useState } from 'react'

export default {
  title: 'StyledButton',
  component: StyledButton,
  argTypes: {
    variant: {
      control: { type: 'radio' },
      options: ['blue', 'green', 'transparent'],
    },
    children: {
      control: { type: 'text' },
    },
  },
} as Meta<typeof StyledButton>

const customizedAction = action('customized')

const Template: StoryFn<StyledButtonProps> = (args) => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
    customizedAction({ currentCount: count });
  };

  return (
    <StyledButton {...args} onClick={handleClick}>
      {args.children}
    </StyledButton>
  );
};

export const TemplateTest = Template.bind({});

TemplateTest.args = {
  variant: 'blue',
  children: '青いボタン'
};

このようにaction関数を利用することで、Storybookの画面からコンポーネントのクリックイベントを再現でき、また、イベントに関連したデータの出力ができるようになりました。
この機能を利用することで、例えば、onClickonSubmitのようなイベントが意図通りに発火しているか、あるいはイベントに渡されるデータが期待通りかどうかを迅速にチェックすることができ、UIの動作確認が効率的に行うことができそうです。

Storybookの機能拡張

ControlsやActionsの機能はStorybookのaddon-essentialsに含まれているアドオンです。
これは.storybook/main.tsでインポートされており、npx sb initで初期化した場合は初めから利用可能となっています。

import type { StorybookConfig } from "@storybook/nextjs";

const config: StorybookConfig = {
  stories: [
    "../stories/**/*.mdx",
    "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
  ],
  addons: [
    "@storybook/addon-onboarding",
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@chromatic-com/storybook",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/nextjs",
    options: {},
  },
  staticDirs: ["../public"],
};
export default config;

もし新しい拡張機能を追加したい場合は、パッケージのインストールに加え.storybook/main.ts のaddons配列にライブラリ名を追記する必要があります。

ドキュメント生成

他の機能についても触れてみます。
例えば、StorybookにはStoryコンポーネントに関するドキュメントを生成できるDocsという機能があります。DocsはControlsとActionsの機能と同様に@storybook/addon-essentialsから提供されています。
以下にStyledButtonコンポーネントのドキュメントを作成してみます。

まずは、ドキュメントの中身をマークダウン形式(mdxファイル)で用意し、そのファイルをStorybookコンポーネントと同じstoriesディレクトリに追加します。

<!-- StyledButton.mdx -->

import { StyledButton } from '../components/StyledButton'

## StyledButton
StyledButtonは`variant`オプションでボタンの色を切り替えることが可能です。

- [Blue](#blue)
- [Green](#green)
- [Transparent](#transparent)

## Blue
```tsx
<StyledButton variant="blue">Blue</StyledButton>
```
<StyledButton variant="blue">Blue</StyledButton>

## Green
```tsx
<StyledButton variant="green">Green</StyledButton>
```
<StyledButton variant="green">Green</StyledButton>

## Transparent
```tsx
<StyledButton variant="transparent">Transparent</StyledButton>
```
<StyledButton variant="transparent">Transparent</StyledButton>

マークダウンファイルが準備できたら、Storybookコンポーネントファイルのメタデータに作成したマークダウンファイルが読み込めるように以下のように設定を追加します。

import { Meta, StoryFn } from '@storybook/react'
import { StyledButton, StyledButtonProps } from '@/components/StyledButton'
import { action } from '@storybook/addon-actions'
import { useState } from 'react'
// 作成したマークダウンファイルをインポート
import MDXDocument from './StyledButton.mdx'

export default {
  title: 'StyledButton',
  component: StyledButton,
  argTypes: {
    variant: {
      control: { type: 'radio' },
      options: ['blue', 'green', 'transparent'],
    },
    children: {
      control: { type: 'text' },
    },
  },
  // メタデータにインポートしたマークダウンドキュメントを参照するオプションを追加
  parameters: {
    docs: {
      page: MDXDocument,
    }
  },
} as Meta<typeof StyledButton>

// ...

これで準備は完了です。StorybookのUI上に作成したコンポーネントのドキュメントが表示されるようになりました。

viewport, 背景色の設定

Storybookのコンポーネント表示エリアの上部を見てみるといくつかアイコンが並んでいます。

これらのアイコンをクリックするとコンポーネントを表示する環境のビューポートの変更や背景色の変更、コンポーネント要素の大きさや設定されているpadding値などを視覚的に確認することができます。

背景色をdarkに変更してみました。

デフォルトで用意されている設定は限られており、背景色の変更はlightモードとdarkモードの切り替えのみ、viewportの変更もSmall Mobile(320px~528px)、Large Mobile(414px~896px)、Tablet(834~1112)の3パターンだけが用意されています。もし背景色やviewportをカスタマイズしたい場合は.storybook/preview.jsでこれらの設定を変更することができます。

以下、.storybook/preview.jsの設定を変更してみます。

import type { Preview } from "@storybook/react";

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    // 背景色の設定
    backgrounds: {
	    values: [
        {
          name: 'grey',
          value: '#808080',
        }
      ]
    },
    // viewportの設定
    viewport: {
      viewports: {
        iphonex: {
          name: 'iPhone X',
          styles: {
            width: '375px',
            height: '812px',
          }
        },
        pc: {
          name: 'PC small',
          styles: {
            width: '890px',
            height: '600px',
          }
        },
      },
    },
  },
};

export default preview;

そうすると設定した灰色の背景色と、iPhoneX用のビューポートサイズに変更することができました。

ただ、注意が必要なのは、.storybook/preview.js へカスタム設定を書き込むとデフォルトの設定は失われてしまうということです。
デフォルトの設定も残しておきたければ、backgroundsオプションのvalues配列やviewportオプションのviewportsに、基本設定用のオプションを書き加えて残しておくことが必要になると感じました。

Linkの設定

Storybook UI上ではコンポーネントをクリックした際のページ遷移も試すことができます。
これには@storybook/addon-linksの機能を利用します。addon-linksaddon-essentialsと同様にnpx sb initコマンドで初期化した時点から追加されているライブラリです。

addon-linksの機能は以下の手順で実装します。

  1. @storybook/addon-links からlinkTo関数をインポートする。
  2. StorybookコンポーネントのonClickイベントの中でlinkTo関数を呼び出す。
  3. linkTo関数は引数を2つ取り、linkTo(Storybookのコンポーネント名, ‘コンポーネントのオプション名’)という形式で呼び出される。
  4. linkTo関数を実装したコンポーネントをクリックすると、linkTo関数で指定したStorybookコンポーネントのオプション名のページへ遷移する。

今回はStyledButtonコンポーネントのうち、BlueボタンをクリックするとGreenボタンのページに、GreenボタンをクリックするとTransparentボタンのページに、TransparentボタンをクリックするとBlueボタンのページへと遷移するような動作を実装してみます。

import { Meta, StoryFn } from '@storybook/react'
import { StyledButton, StyledButtonProps } from '@/components/StyledButton'
import { action } from '@storybook/addon-actions'
import { useState } from 'react'
import MDXDocument from './StyledButton.mdx'
// linkTo関数のインポート
import { linkTo } from '@storybook/addon-links'

export default {
  title: 'StyledButton',
  component: StyledButton,
  argTypes: {
    variant: {
      control: { type: 'radio' },
      options: ['blue', 'green', 'transparent'],
    },
    children: {
      control: { type: 'text' },
    },
  },
  parameters: {
    docs: {
      page: MDXDocument,
    }
  },
} as Meta<typeof StyledButton>

// ...

export const Blue: StoryFn<StyledButtonProps> = (args) => {
  return (
    <StyledButton {...args} variant='blue' onClick={linkTo('StyledButton', 'Green')}>Greenボタンのページへ行きます</StyledButton>
  )
}

export const Green: StoryFn<StyledButtonProps> = (args) => {
  return (
    <StyledButton {...args} variant='green' onClick={linkTo('StyledButton', 'Transparent')}>
      Transparentボタンのページへ行きます</StyledButton>
  )
}

export const Transparent: StoryFn<StyledButtonProps> = (args) => {
  return (
    <StyledButton {...args} variant='transparent' onClick={linkTo('StyledButton', 'Blue')}>Blueボタンのページへ行きます</StyledButton>
  )
}

ボタンクリック時のページ遷移の動きをGifに収めてみましたが、分かりにくいですね…。

遷移を再現するためには遷移元、遷移先の各Storybookコンポーネントを明示的に用意する必要がありそうです。
また、今回はStyledButton以外のコンポーネントは用意しませんでしたが、別のStroybookコンポーネントを用意すればそのコンポーネントへの遷移も試すことができそうです。

以上、Storybookについてふれてみた記録でした。
もうちょっと触ってみて引き続き扱いに慣れていきたいと思います。

Discussion