【Storybook】一覧性のある Story は良いぞ

# はじめに
[ユニークビジョン株式会社](https://qiita.com/organizations/uniquevision)では、フロントエンドの開発ツールおよびドキュメントツールとして、Storybook を使っています。また、 Chromatic を利用することで、VRT（ビジュアルリグレッションテスト）ツールとしても活用しています。

https://storybook.js.org/

https://www.chromatic.com/

これまで私は、Story を書くときに Args を利用して様々な値の入力をテストしていましたが、**最近は見たい入力の組み合わせをすべて表示する方法を選択することが多くなりました。** 本記事ではそれぞれの方法とメリットとデメリットをご紹介します。

# サンプルコンポーネントの仕様
この記事で用いるコンポーネントは次のようなものとします。
- 見た目が 青 / 赤 の 2 通りある
- 活性 / 非活性を制御できる
- 文字が少ないときはその文字の大きさに合わせて縮小する
- 最大幅より文字が多いときは省略表示にする

この仕様を基に作ったのが下記のコンポーネントです。

```SampleButton.vue
<template>
  <button
    class="p-2 text-white truncate"
    style="max-width: 8rem;"
    :disabled="disabled"
    :class="
      disabled
        ? 'bg-gray-500 cursor-default'
        : variant === 'blue'
          ? 'bg-blue-500'
          : 'bg-red-500'
    "
  >
    {{ label }}
  </button>
</template>

<script lang="ts" setup>
import { PropType } from 'vue';

defineProps({
  variant: {
    type: String as PropType<'blue' | 'red'>,
    default: 'blue',
  },
  label: {
    type: String,
    default: '',
  },
  disabled: {
    type: Boolean,
    default: false,
  },
});

</script>

```

# Args を利用した方法
Args で Prop の値を変えられるようにした Story を用いる方法です。

```SmapleComponent.stories.ts
import { Story } from '@storybook/vue3';
import { defineComponent } from 'vue';

import SampleButton from './SampleButton.vue';

export default {
  title: 'samples/SampleButton',
  component: SampleButton,
  argTypes: {
    variant: {
      control: {
        type: 'select',
        options: ['blue', 'red'],
      },
    },
    label: {
      control: {
        type: 'select',
        options: ['普通の長さ', '長い長い長い長い長いラベル'],
      },
      defaultValue: '普通の長さ',
    },
    disabled: {
      control: {
        type: 'boolean',
      },
    },
  },
};

export const WithArgs: Story = args => defineComponent({
  components: {
    SampleButton,
  },
  setup() {
    return args;
  },
  template: `
    <SampleButton
      :variant="variant"
      :label="label"
      :disabled="disabled"
    />
  `,
});
```

開発時は値を変化させて様々な状態を確認していくイメージです。

![withargs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/a7e0f39d-8991-9a9a-79ca-8feeb55491ba.gif)


## メリット
- 値の入力を自由に選択して表示することができるので、入力の組み合わせ数が非常に多い時に有効。

## デメリット
- 特定の組み合わせを見たいときに手で入力値を変える手間がかかる。（＝開発時にエディタとブラウザを行ったり来たりする必要がある。）

# 一覧性のある Story
予め見たいパターンを Story として羅列しておきます。

```SmapleComponent.stories.ts
import { Story } from '@storybook/vue3';
import { defineComponent } from 'vue';

import SampleButton from './SampleButton.vue';

export default {
  title: 'samples/SampleButton',
  component: SampleButton,
};

export const AllPattern: Story = () => defineComponent({
  components: {
    SampleButton,
  },
  template: `
    <div class="flex flex-col items-start space-y-3">
      <SampleButton
        variant="blue"
        label="普通の長さ"
        :disabled="false"
      />
      <SampleButton
        variant="red"
        label="普通の長さ"
        :disabled="false"
      />
      <SampleButton
        variant="blue"
        label="長い長い長い長いラベル"
        :disabled="false"
      />
      <SampleButton
        variant="red"
        label="長い長い長い長いラベル"
        :disabled="false"
      />
      <SampleButton
        variant="blue"
        label="普通の長さ"
        :disabled="true"
      />
      <SampleButton
        variant="red"
        label="普通の長さ"
        :disabled="true"
      />
      <SampleButton
        variant="blue"
        label="長い長い長い長いラベル"
        :disabled="true"
      />
      <SampleButton
        variant="red"
        label="長い長い長い長いラベル"
        :disabled="true"
      />
    </div>
  `,
});

```

開発時はすべての状態を確認しながら進めていきます。

![allpatterns.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/27c140ae-7465-b31d-854e-16d20ea726c0.gif)

## メリット
- コンポーネントのあらゆる状態を見ながら開発できる。（＝ A の状態のスタイルを調整している間に、B の状態のスタイルが崩れたりした場合、すぐに気付ける。）
- どのような見た目、ロジックをもつコンポーネントなのかが一目でわかる。
- あらゆる状態のスナップショットが取られるので、差分が出たときに検知しやすい。
- スナップショット枚数を抑えることができるので、Chromatic の費用を抑えることができる。

## デメリット
- 組み合わせ数が多くなりすぎるときに見づらくなってしまう。

# まとめ
どちらの方法がより優れている、という訳では無いので、柔軟に組み合わせて使うのが良いと思われます。
例えば、
- **開発者やデザイナーが確認するプレイグラウンドとして、Args を用いた Story を用意する。**
- **スナップショット差分の検知をしやすくするために、あるいは開発速度向上のために一覧性のある Story を用意する。**

のようなケースが考えられますが、両方を採用するのもありだと思います。

# 最後に

フロントエンドのことをよく呟いています。良ければフォローお願いします！
<a href="https://twitter.com/punksy2?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">Follow @punksy2</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>