いろいろな Storybook Doc blocks


こんにちは。フロントエンドエンジニアの辻です。
前回に引き続き、他の Storybook Doc blocks について触れていこうと思います。

Storybook Docs ページのカスタマイズ

Storybook Doc blocks に触れていく前に、まず Docs ページのカスタマイズ方法を紹介します。
前回の Typeset のサンプルコードでチラリと登場していましたが、Storybook v7 では Docs ページのテンプレートを簡単にカスタマイズできます。

例えば、以下のような Tag コンポーネントとその Storybook ファイルがあるとします。

import { PropsWithChildren } from 'react'

type Tag = {
  type?: 'primary' | 'secondary'
}

export const Tag = ({ type = 'primary', children }: PropsWithChildren<Tag>) => {
  return (
    <span
      className={`
      ${type === 'primary' ? 'border-blue-500' : 'border-green-500'}  
      ${type === 'secondary' ? 'text-blue-500' : 'text-green-500'}
      font-bold py-1 px-2 rounded inline-flex border text-sm`}
    >
      {children}
    </span>
  )
}
import type { Meta, StoryObj } from '@storybook/react'
// ↓@storybook/blocks から import …(①)
import {
  Title,
  Subtitle,
  Description,
  Primary,
  Controls,
  Stories
} from '@storybook/blocks'

import { Tag } from './tag'

const meta = {
  title: 'Components/Tag',
  component: Tag,
  parameters: {
    layout: 'centered',
    docs: {
      // ↓ meta オブジェクトの parameters.docs.page に Stoybook Doc blocks を返す関数をセットします …(②)
      page: () => (
        <>
          <Title />
          <Subtitle />
          <Description />
          <Primary />
          <Controls />
          <Stories />
        </>
      )
    }
  },
  argTypes: {
    type: {
      options: ['primary', 'secondary'],
      control: { type: 'radio' }
    }
  }
} satisfies Meta<typeof Tag>

export default meta
type Story = StoryObj<typeof meta>

export const PrimaryTag: Story = {
  args: {
    children: 'Primary タグ',
    type: 'primary'
  }
}

export const SecondaryTag: Story = {
  args: {
    children: 'Secondary タグ',
    type: 'secondary'
  }
}

肝となるのは、@storybook/blocks から import した Doc Blocks コンポーネント群(①)と、meta オブジェクトの parameters.docs.page にセットした関数(②)です。

parameters.docs.page の関数にて返される結果が、そのまま Docs ページの内容となります。したがって、この関数の返り値を調整する事で Docs ページを柔軟にカスタマイズできます。
自作したコンポーネントを parameters.docs.page の関数の返り値に含める事も可能です。

また、一つ一つの .stories.tsx を変更せずとも、.storybook/preview.tsx を更新する事で、すべての Docs ページのカスタマイズが可能です。

Storybook 公式サイトの Write a custom template にある通り、.storybook/preview.tsx を以下のように更新すれば、すべての Docs ページが preview.parameters.docs.page の関数の返り値の形式になります。

import { Preview } from '@storybook/your-framework';
// ↓@storybook/blocks から import
import { Title, Subtitle, Description, Primary, Controls, Stories } from '@storybook/blocks';

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
    // preview.parameters.docs.page に Doc blocks を返す関数をセットします
    docs: {
      page: () => (
        <>
          <Title />
          <Subtitle />
          <Description />
          <Primary />
          <Controls />
          <Stories />
        </>
      ),
    },
  },
};

export default preview;

いろいろな Storybook Doc blocks

さて、Docs ページのカスタマイズ方法が分かったところで、公式で用意されているいろいろな Storybook Doc blocks を見ていこうと思います。

Source

文字通り、コンポーネントのソースコードを表示する block です。
先程の Tag コンポーネントで使ってみます。

import type { Meta, StoryObj } from '@storybook/react'
import { renderToStaticMarkup } from 'react-dom/server' // ← 新規追加
import {
  Title,
  Subtitle,
  Description,
  Primary,
  Controls,
  Stories,
  Source // ← 新規追加
} from '@storybook/blocks'

import { Tag } from './tag'

// …略…

// ↓ 新規追加
export const SourceStory: Story = {
  render: () => <Source code={renderToStaticMarkup(<Tag>タグ</Tag>)} />
}

Stroybook を起動すると、下記のようなコードスニペットが表示されます。

「Copy」ボタンからコンポーネントのソースコードをコピーできます。
React や Vue のような JavaScript フレームワークを使わずに、シンプルなHTML・CSS で UI を構築している場合に重宝するでしょう。

> Source | Stroybook

ColorPalette

ColorPalette はカラーを一覧化するために利用します。
使い方は ColorPalette コンポーネントを用意して、その中に ColorItem コンポーネントを列挙していくのみです。
その際に、ColorItem コンポーネントの colors に表示するカラーをオブジェクト形式で登録していきます。

例えば、以下のような colors.stories.tsx を用意した上で Stroybook を起動してみます。

import type { Meta, StoryObj } from '@storybook/react'
import { Title, ColorPalette, ColorItem } from '@storybook/blocks'

const BlankComponent = () => <div></div>

const meta: Meta<typeof BlankComponent> = {
  title: 'Components/Colors',
  component: BlankComponent,
  parameters: {
    docs: {
      page: () => (
        <>
          <Title>Colors</Title>
          <ColorPalette>
            <ColorItem
              title='theme.color.gray'
              subtitle='gray colors'
              colors={{
                '50': '#f9fafb',
                '100': '#f3f4f6',
                '200': '#e5e7eb',
                '300': '#d1d5db',
                '400': '#9ca3af',
                '500': '#6b7280',
                '600': '#4b5563',
                '700': '#374151',
                '800': '#1f2937',
                '900': '#111827',
                '950': '#030712'
              }}
            />
            <ColorItem
              title='theme.color.blue'
              subtitle='blue colors'
              colors={{
                '50': '#eff6ff',
                '100': '#dbeafe',
                '200': '#bfdbfe',
                '300': '#93c5fd',
                '400': '#60a5fa',
                '500': '#3b82f6',
                '600': '#2563eb',
                '700': '#1d4ed8',
                '800': '#1e40af',
                '900': '#1e3a8a',
                '950': '#172554'
              }}
            />
            <ColorItem
              title='theme.color.green'
              subtitle='green colors'
              colors={{
                '50': '#f0fdf4',
                '100': '#dcfce7',
                '200': '#bbf7d0',
                '300': '#86efac',
                '400': '#4ade80',
                '500': '#22c55e',
                '600': '#16a34a',
                '700': '#15803d',
                '800': '#166534',
                '900': '#14532d',
                '950': '#052e16'
              }}
            />
          </ColorPalette>
        </>
      )
    }
  }
}
export default meta
type Story = StoryObj<typeof BlankComponent>

export const Base: Story = {
  render: () => <BlankComponent />
}

すると、以下のようなカラーの一覧が表示されます。
プロジェクトで利用するカラーをまとめるのに便利ですね。

> ColorPalette | Stroybook

IconGallery

IconGallery はアイコンを一覧化するためのモノで、ColorPalette と使い方が似ています。
IconGallery の場合は、IconGallery コンポーネントを用意して、その中に IconItem コンポーネントを列挙していくのみです。

例えば、以下のような icon.stories.tsx を用意した上で Stroybook を起動してみます。

import type { Meta, StoryObj } from '@storybook/react'
import { Title, IconGallery, IconItem } from '@storybook/blocks'

const BlankComponent = () => <div></div>

const meta: Meta<typeof BlankComponent> = {
  title: 'Components/Icons',
  component: BlankComponent,
  parameters: {
    docs: {
      page: () => (
        <>
          <Title>Icons</Title>
          <IconGallery>
            <IconItem name='Arrow up'>
              <img src='/assets/images/icons/arrow-up.svg' alt='arrow up' />
            </IconItem>
            <IconItem name='Arrow right'>
              <img
                src='/assets/images/icons/arrow-right.svg'
                alt='arrow right'
              />
            </IconItem>
            <IconItem name='Arrow left'>
              <img src='/assets/images/icons/arrow-left.svg' alt='arrow left' />
            </IconItem>
            <IconItem name='Arrow down'>
              <img src='/assets/images/icons/arrow-down.svg' alt='arrow down' />
            </IconItem>
            <IconItem name='Cloud'>
              <img src='/assets/images/icons/cloud.svg' alt='cloud' />
            </IconItem>
            <IconItem name='Cloud upload'>
              <img
                src='/assets/images/icons/cloud-upload.svg'
                alt='cloud-upload'
              />
            </IconItem>
            <IconItem name='Cloud download'>
              <img
                src='/assets/images/icons/cloud-download.svg'
                alt='cloud-download'
              />
            </IconItem>
            <IconItem name='Copyright'>
              <img src='/assets/images/icons/copyright.svg' alt='copyright' />
            </IconItem>
          </IconGallery>
        </>
      )
    }
  }
}
export default meta
type Story = StoryObj<typeof BlankComponent>

export const Base: Story = {
  render: () => <BlankComponent />
}

すると、以下のようなアイコンの一覧が表示されます。
プロジェクトで利用するアイコンをまとめるのに便利です。

> IconGallery | Stroybook

まとめ

Stroybook に備わったいろいろな Storybook Doc blocks を見てきました。
Storybook を導入した際に、UI を Storybook で閲覧できるようにするだけで留まってしまう場合も多いですが、Storybook Doc blocks を使ってカスタマイズすれば、よりプロジェクト全体のUIを見通しやすくなります。

この機会に、オンラインで気軽に面談してみませんか?

現在弊社では一緒にお仕事をしてくださるエンジニアさんやデザイナーさんを積極募集しています。まずはカジュアルな面談で、お互いに大事にしていることをお話できたらうれしいです。詳しい応募要項は以下からチェックしてください。

パートナー契約へのお問い合わせもお仕事へのお問い合わせも、どちらもいつでも大歓迎です。まずはオンラインでのリモート面談からはじめましょう。ぜひお気軽にお問い合わせください!

お問い合わせしてみる!

投稿者 Tsuji Atsuhiro

フロントエンドエンジニア。 DTP・Webデザイナーを経験した後、フロントエンドエンジニアに転向。HTML/CSS/JavaScriptを中心にWeb開発を担当してきました。 UI・UXに興味があり、デザイン・コーディング両面から考えられるデザインエンジニアを目指しています。 普段はマラソンやボクシングなどで体を動かしてます。