以下の内容はhttps://tech.itandi.co.jp/entry/2025/03/11/120616より取得しました。


デザインシステム「ITANDI BB UI」とそれを支えるStorybookの紹介

こんにちは!イタンジのFrontend Unit所属の薄羽です。2024年3月に中途入社しました。好きなnpmパッケージは @emotion/react です。

イタンジのFrontend Unitには主務で薄羽と西野が在籍しており、デザインシステム「ITANDI BB UI」の開発とプロダクトのフロントエンドを横断的に見ること*1を主な仕事としています。

今回はITANDI BB UIとそれを支える「Storybook」を紹介します。

ITANDI BB UI

イタンジには、物件管理や申込管理など、様々な機能が存在します。それらの機能は別々のチームで開発されており、同じ見た目や振る舞いのコンポーネント(e.g., <Button>)をそれぞれのチームが実装していました。まったく同じ見た目のコンポーネントをそれぞれのチームで実装し直す必要はないはずですし、チームの裁量によって見た目や振る舞いに若干の差異が生まれる可能性もあります。

イタンジのユーザーは複数の機能をシームレスに移動するため、どの機能を使っても共通の見た目や挙動を期待します。たとえば、特定の機能でだけ <Button> の見た目や <Dropdown> の挙動が違うことはあってはならないことです。

ITANDI BB UIのコンポーネントはReactで実装されており、それを各チームが使うことで、機能間で共通のUI/UXを実現しています。コンポーネントの見た目や振る舞いはデザイナに作成していただいているFigmaで管理しています。

最初のコミットは、2021年11月5日にCTOの大原が行いました 🎉

initial commit 🎉

2025年2月(執筆現在)では、133個のコンポーネントがexportされています!

Storybookのコンポーネント一覧から一部を掲載

コンポーネントの実装には、コンポーネントライブラリを使用せず、依存パッケージも極力少なくしています。 DatePickerやComboBoxなども頑張って実装しています!

DatePicker

ComboBox

Storybook

ITANDI BB UIのStorybookは頑張って色々なことを行っています!その一部を紹介します!*2

VRT

ITANDI BB UIのStorybookにはVRT(Visual Regression Test)が導入されています。毎Pull Requestで、Storybook Test runner(.storybook/test-runner.ts )によって各ストーリーファイルのスクリーンショットを撮り、Amazon S3に保存されているベースブランチのスクリーンショットとの比較を行っています。VRTにはreg-suitを使っています。導入当時はstorycap-testrunでCSSアニメーションを有効にできなかったため、test-runner.ts でスクリーンショットを撮る処理を書きました。

Storybookの背景にイタンジのロゴの透かしを入れるようにしています。Storybookの背景はデフォルトで白になっており、これだとコンポーネントの背景が白だから白く見えるのか、コンポーネントの背景が透明で後ろの背景が白だから白く見えるのかの区別がつきません。透かしを入れることで、白背景の設定のし忘れに気づけるようにしています。*3

背景にロゴの透かしが入っている

CSSアニメーションを停止しないと、<Spinner> のような「CSSアニメーションによって回転するコンポーネント」では、スクリーンショットが撮られるタイミングによって、回転している差分だけスクリーンショットの差分が生まれてしまいます。そのため、CI(Storybook Test runner)上では以下のCSS( .stop-animation )によってアニメーションを停止しています。

また、<NotificationBar> のような「アニメーションによって通知が出現し、一定時間後に消えるコンポーネント」では、アニメーションの時間分*4だけTest runnerの実行時間が伸びてしまいます。そのため、CI上では、.instant-animation によって即時にCSSアニメーションを終了するようにしています。

.stop-animation * {
    animation-play-state: paused !important;
}

.instant-animation * {
    animation-duration: 0.001s !important;
    transition-duration: 0.001s !important;
}

ストーリーの種類

ITANDI BB UIのStorybookにはいくつかのストーリーの定義*5があります。以下に代表的な2つのストーリーを紹介します。

Sampleストーリー

Props(e.g., variant)による見た目の変化が一覧できるストーリーです。ITANDI BB UIユーザに実装例を明示する意図もありますが、前述の通り、このストーリーのVRTを実行することで、意図しない見た目の変更に気づけるようにしています。

ButtonのSampleストーリー

Playストーリー

Play functionが定義されているインタラクションテスト用のストーリーです。見た目の変更はVRTで検知できますが、Propsが正しく渡されているかなどは確認できません。そのため、コンポーネントを実装するときは、Propsやコンポーネント内のロジックに対してPlayストーリーを書くようにしています(現在、306個のPlayストーリーが存在します!)。

Play functionによるインタラクションテストはFlakyになりがちです。ITANDI BB UIのStorybookでは、play に渡す関数を try-catch で囲み、インタラクションテストの結果によってCIを落ちないようにしています。代わりに、テスト結果を左上に <Chip> で表示し、スクリーンショット上で確認できるようにしています。テストが成功したときは、「Passed」、失敗したときは「Failed」と表示されるため、テスト結果によってスクリーンショットの差分が生まれ、VRTでもどのテストが失敗したのかがわかります。

Chipによるテスト結果の表示

href のテスト

少しニッチですが、意外と困った & 参考記事がなかったため紹介します。<Link> のようなコンポーネントを実装したとき、Propsの href がコンポーネント内の <a> に正しく渡っているか、また、onClick などによって遷移が妨げられていないかをテストしたいと思います。

ただ、href にハッシュのみを設定したとしても、以下のようなPlay functionでは、Storybook上でそのストーリーを閲覧すると別のページに遷移されてしまいます。

{
    play: async () => {
        const user = userEvent.setup();
        await user.click(document.querySelector("a"));
    }
}

これは、ストーリーが実際には iframe.html で表示され、その <iframe> 内で <base target="_parent"> が設定されているからです(i.e., <iframe>iframe.html#hash に遷移するようになっています)。そのため、<base>target_self に書き換え、hashchange を使うと、<a> の遷移が正しく動作しているかをテストできます!

{
    play: async () => {
        const onHashChange = fn();
        const base = document.head.querySelector("base");

        if (base.target === "_parent") {
            base.target = "_self";
        }

        window.addEventListener("hashchange", () => { onHashChange(); }, { once: true });

        const user = userEvent.setup();
        await user.click(document.querySelector("a"));
        await expect(onHashChange).toBeCalledWith();
    }
}

argTypesPlugin

ITANDI BB UIのすべてのコンポーネントのPropsは type-festReadonlyDeep で囲われています。そのため、そのままControlsに表示されると型が以下のように表示されてしまいます。

argTypesPluginを使用する前

ITANDI BB UIのStorybookでは、 argTypesPlugin というものを実装し、以下のように ReactNode として表示されるようになっています!型エイリアスも良い感じに展開されるようになっています。

argTypesPluginを使用した後

ITANDI BB UIを支えるスクリプト

Storybookの技術ではないのですが、ITANDI BB UIには便利スクリプトが2つ存在します。

1つは、「どこでどのコンポーネントが使われているかを表示する」スクリプトです。たとえば、以下では variant が渡されている <Button> のソースコードの箇所をURLで出力してくれます。これによってどのコンポーネントの修正が、どの機能のソースコードに影響があるのかを把握できます!

$ command Button --has variant

https://github.com/itandi/(...)#L149
https://github.com/itandi/(...)#L234
https://github.com/itandi/(...)#L239

もう1つは、「コンポーネントが指定されたPropsを持つかを表示する」スクリプトです。Propsから classNamestyle を削除しようとしましたが、どのコンポーネントがそのPropsを持つのかを把握できていなかったため、実装しました!

$ command className style

┌───────────────────────┬───────────┬───────┐
│        (index)        │ className │ style │
├───────────────────────┼───────────┼───────┤
│         Alert         │   truefalse │
│        Avatar         │   truefalse │
│         Badge         │   truefalse

おわりに

実装されているコンポーネントの数を考えると、ITANDI BB UIはコンポーネントライブラリとしては十分だと思います。一方で、カラーパレットやデザイントークンがまだ整理しきれておらず、デザインシステムとしてはそこが課題だと思っています。今後も、なめらかな不動産取引を実現するために、ITANDI BB UIをデザインシステムとしてさらに洗練させていきます!

*1:最近は、webpack v4をViteにしたり…

*2:頑張りはしましたが、エンジニアからは「コードを見た方が使い方がわかる」と言われますし、残念ながら私もそう思います 😢

*3:実際、このPull Requestを作成したとき、VRTによっていくつかのコンポーネントの背景の設定し忘れが検知されました

*4:ITANDI BB UIの場合では150 msで完全に登場し、10 sで消えます

*5:接頭辞で区別されています




以上の内容はhttps://tech.itandi.co.jp/entry/2025/03/11/120616より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14