ISID テックブログ

ISIDが運営する技術ブログ

Storybook6(CSF)の基本実装

こんにちは。ISID コミュニケーションIT事業部 瀧川亮弘(あきひろ)です。
プロジェクトにてStorybookを利用しています。
Storybookの概要と基本的な実装方法をお伝えできればと思います。

Storybookとは?

一言でいうとUIのカタログです。また、それを作成するためのライブラリです。
Storybookを利用することで、GUI上でUIコンポーネントのデザインや振る舞いを簡単に確認できます。

百聞は一見にしかずということで、普段大変お世話になっているVSCodeStorybookのリンクを貼っておきます。良かったら触ってみてください!
Microsoft VSCode Webview UI Toolkit

Storybookの実装

UIコンポーネントStorybook上で表示するために.stories.jsという拡張子のファイルを作成する必要があります。
本章では、こちらのファイルの基本的な実装について記載します。

尚、いくつか存在する記法のうち、CSF(Component Story Format)という記法を採用します。以前まで主流だった、storiesOf関数やadd関数を利用するstoriesOf APIは現在非推奨となっています。

余談ですが、筆者はstoriesOf APIで記載された数十のファイルをCSFに書き換える作業をしたことがあります。個々の作業は簡単でしたが、数が数だけにとても苦労した懐かしい思い出があります。

対象のコンポーネント

対象とするコンポーネントとして、簡単なボタンを用意しました。
愛着が湧くように自身の名前をつけてAkiButtonと名付けました。
Vue3script setup構文を利用しています。

<template>
  <button class="button" :class="[getColor]" @click="handleClick">
    <slot />
  </button>
</template>

<script setup lang="ts">
import { computed } from "vue";
interface Props {
  /**
   * 色
   */
  color?: "basic" | "primary" | "error";
}
const props = withDefaults(defineProps<Props>(), {
  color: "basic",
});

interface Emits {
  /**
   * クリックイベント
   */
  (e: "click"): void;
}
const emit = defineEmits<Emits>();
const handleClick = () => {
  emit("click");
};

const getColor = computed<string>(() => `button__color--${props.color}`);
</script>

<style lang="scss" scoped>
.button {
  height: 30px;
  padding: 0 16px;
  cursor: pointer;

  &__color {
    &--basic {
      background-color: $color__primary-light;
      border-color: $color__primary-light;
      color: $color__primary;
    }
    &--primary {
      background-color: $color__primary;
      border-color: $color__primary;
      color: $color__white;
    }
    &--error {
      background-color: $color__error;
      border-color: $color__error;
      color: $color__white;
    }
  }
}
</style>

実装

完成形

まずは完成形をお見せします。
次章から順を追って実装を確認します。

import AkiButton from "./AkiButton.vue";
export default {
  title: "Atoms/AkiButton",
  component: AkiButton,
  argTypes: {
    color: {
      control: {
        type: "select",
        options: ["basic", "primary", "error"],
      },
    },
    click: {
      action: "click",
    },
  },
};

const Template = (args, { argTypes }) => ({
  components: { AkiButton },
  setup() {
    return {
      ...args,
    };
  },
  template: `
    <AkiButton :color="color" @click="click">あきボタン</AkiButton>
  `,
});

export const Basic = Template.bind({});
Basic.args = {
  color: "basic",
};

export const Primary = Template.bind({});
Primary.args = {
  color: "primary",
};

export const Error = Template.bind({});
Error.args = {
  color: "error",
};

メタデータの定義

メタデータを定義します。
titleには、メニューに表示される名前を定義します。/(スラッシュ)で階層を切ると、GUI上ツリー表示されます。ちなみに、現在のプロジェクトではAtomic Designに基づいて階層を切っています。

componentには、対象のコンポーネントで用意したコンポーネントを指定しています。

これらを指定したオブジェクトをデフォルトエクスポートすることでメタデータとして認識してくれます。

import AkiButton from "./AkiButton.vue";
export default {
  title: "Atoms/AkiButton",
  component: AkiButton,
};

テンプレートの定義

テンプレートを定義します。
次章でいくつかのストーリーを定義しますが、それらのストーリーが共通して利用するテンプレートとなります。各ストーリーで利用する関数なので、テンプレート自体のexportは必要ありません。

テンプレートの関数は、引数として各ストーリーごとに定義されたプロパティを受け取ります。ここではargsという名前で受け取ったプロパティを対象コンポーネントに渡しています。

const Template = (args, { argTypes }) => ({
  components: { AkiButton },
  setup() {
    return {
      ...args,
    };
  },
  template: `
    <AkiButton :color="color" @click="click">あきボタン</AkiButton>
  `,
});

ストーリーの定義

Storybookのメインであるストーリーを定義します。
コンポーネントを表示するパターン、テストするパターンなどを考慮してストーリーを表現します。ここでは、colorプロパティの値ごとに、ストーリーを作成しています。
テンプレートの定義で用意したテンプレートの関数をもとにいくつかのストーリーを作成するのが便利です。

ストーリーは関数で表現し、名前付きエクスポートします。
ここでつけた名前がデフォルトではストーリー名となります。

export const Basic = Template.bind({});
Basic.args = {
  color: "basic",
};

export const Primary = Template.bind({});
Primary.args = {
  color: "primary",
};

export const Error = Template.bind({});
Error.args = {
  color: "error",
};

アドオンを利用する

アドオンとはStorybookの拡張モジュールのことです。
今回はStorybookが公式でサポートしている3つのアドオンを利用してみます。

Controlsアドオン

Controlsアドオンにより、コンポーネントに対するプロパティをGUI上で動的に切り替えることができます。

  argTypes: {
    color: {
      control: {
        type: "select",
        options: ["basic", "primary", "error"],
      },
    },
  },

Actionsアドオン

Actionsアドオンにより、発火されたイベントをロギングします。
各イベントが発火するタイミングやイベントの内容を確認できます。

  argTypes: {
    click: {
      action: "click",
    },
  },

Docsアドオン

Docsアドオンは、ソースコードを解析しドキュメントを自動生成してくれます。
Vueファイルに記載したコメントがドキュメントに反映されていることがわかります。

  /**
   * 色
   */
  color?: "basic" | "primary" | "error";

storybook実装方法は以上です。
必要に応じて、アドオンを追加するとよいでしょう。

SDD(Storybook Driven Development)のすゝめ

Storybookとは?ではStorybookをUIカタログとご紹介しましたが、StorybookをUIの開発環境と捉えることもできます。

UIコンポーネントをアプリケーションから切り離された環境(Storybook上)で開発することには以下のようなメリットがあると思います。

皆さんもよければ、Storybook上で開発してみてください。

最後に

いかがでしたか?
Storybookって面白いなと思っていただけたら嬉しいです!

では、よいStorybook生活を!

執筆:@takigawa.akihiro、レビュー:@shibata.takaoShodoで執筆されました