mcpField
S
storybook-setup

storybook-setup

github.com/patricio0312rev開発63321 回インストール41 週間前に更新
リポジトリ

storybook-setup について

Storybook を設定するフロントエンドチーム向けのガイドです。コンポーネントストーリー、MDX ドキュメント、操作・ビジュアルテスト、静的公開を扱います。

主な機能

  1. 01Storybook の初期化と設定
  2. 02CSF3 のコンポーネントストーリー
  3. 03MDX によるコンポーネント文書
  4. 04操作テストとアクセシビリティテスト
  5. 05Chromatic によるビジュアル回帰テスト
  6. 06静的ビルドと公開

活用シーン

  1. 01操作可能なコンポーネント文書の作成
  2. 02フォーム操作とアクセシビリティのテスト
  3. 03Storybook 静的サイトの公開

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/patricio0312rev/skills --skill storybook-setup

インストール後、Claude Code で/storybook-setupを入力して読み込まれたか確認します。

詳細

Storybook のセットアップ

包括的なコンポーネントのドキュメント作成とテストのために、Storybook を設定します。

基本的なワークフロー

  1. Storybook を初期化する: フレームワークを使ってセットアップします
  2. アドオンを設定する: Controls、actions、a11y を追加します
  3. ストーリーを作成する: コンポーネントをドキュメント化します
  4. ドキュメントを追加する: MDX ページを作成します
  5. テストをセットアップする: ビジュアルリグレッションテストを行います
  6. ドキュメントをデプロイする: 静的ホスティングを利用します

インストール

# Initialize Storybook
npx storybook@latest init

# Or with specific framework
npx storybook@latest init --type react
npx storybook@latest init --type nextjs
npx storybook@latest init --type vue3

設定

メイン設定
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: [
    '../src/**/*.mdx',
    '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
  ],

  addons: [
    '@storybook/addon-onboarding',
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@chromatic-com/storybook',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-designs',
    '@storybook/addon-coverage',
  ],

  framework: {
    name: '@storybook/react-vite',
    options: {},
  },

  docs: {
    autodocs: 'tag',
  },

  staticDirs: ['../public'],

  typescript: {
    reactDocgen: 'react-docgen-typescript',
    reactDocgenTypescriptOptions: {
      shouldExtractLiteralValuesFromEnum: true,
      shouldRemoveUndefinedFromOptional: true,
      propFilter: (prop) =>
        prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
    },
  },

  viteFinal: async (config) => {
    // Customize Vite config
    return config;
  },
};

export default config;
プレビュー設定
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
import { themes } from '@storybook/theming';
import '../src/styles/globals.css';

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#1a1a1a' },
        { name: 'gray', value: '#f5f5f5' },
      ],
    },
    layout: 'centered',
    docs: {
      theme: themes.light,
    },
    a11y: {
      config: {
        rules: [
          { id: 'color-contrast', enabled: true },
          { id: 'label', enabled: true },
        ],
      },
    },
    viewport: {
      viewports: {
        mobile: {
          name: 'Mobile',
          styles: { width: '375px', height: '667px' },
        },
        tablet: {
          name: 'Tablet',
          styles: { width: '768px', height: '1024px' },
        },
        desktop: {
          name: 'Desktop',
          styles: { width: '1440px', height: '900px' },
        },
      },
    },
  },
  globalTypes: {
    theme: {
      description: 'Global theme',
      defaultValue: 'light',
      toolbar: {
        title: 'Theme',
        icon: 'circlehollow',
        items: ['light', 'dark'],
        dynamicTitle: true,
      },
    },
  },
  decorators: [
    (Story, context) => {
      const theme = context.globals.theme;
      return (
        <div className={theme === 'dark' ? 'dark' : ''}>
          <div className="bg-white dark:bg-gray-900 p-4">
            <Story />
          </div>
        </div>
      );
    },
  ],
};

export default preview;

ストーリーの作成

コンポーネントストーリー形式(CSF3)
// src/components/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import { Button } from './Button';

const meta = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
    docs: {
      description: {
        component: 'A versatile button component with multiple variants and sizes.',
      },
    },
  },
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'outline', 'ghost'],
      description: 'Visual style variant',
      table: {
        type: { summary: 'string' },
        defaultValue: { summary: 'primary' },
      },
    },
    size: {
      control: 'radio',
      options: ['sm', 'md', 'lg'],
      description: 'Button size',
    },
    disabled: {
      control: 'boolean',
      description: 'Disable the button',
    },
    loading: {
      control: 'boolean',
      description: 'Show loading state',
    },
    children: {
      control: 'text',
      description: 'Button content',
    },
  },
  args: {
    onClick: fn(),
    children: 'Button',
  },
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

// Basic stories
export const Primary: Story = {
  args: {
    variant: 'primary',
  },
};

export const Secondary: Story = {
  args: {
    variant: 'secondary',
  },
};

export const Outline: Story = {
  args: {
    variant: 'outline',
  },
};

export const Ghost: Story = {
  args: {
    variant: 'ghost',
  },
};

// Size variants
export const Small: Story = {
  args: {
    size: 'sm',
  },
};

export const Large: Story = {
  args: {
    size: 'lg',
  },
};

// States
export const Disabled: Story = {
  args: {
    disabled: true,
  },
};

export const Loading: Story = {
  args: {
    loading: true,
  },
};

// With icons
export const WithIcon: Story = {
  args: {
    children: (
      <>
        <PlusIcon className="mr-2 h-4 w-4" />
        Add Item
      </>
    ),
  },
};

// All variants showcase
export const AllVariants: Story = {
  render: () => (
    <div className="flex flex-wrap gap-4">
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
    </div>
  ),
};
インタラクティブなストーリー
// src/components/Form/Form.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { within, userEvent, expect, fn } from '@storybook/test';
import { Form } from './Form';

const meta: Meta<typeof Form> = {
  title: 'Components/Form',
  component: Form,
  args: {
    onSubmit: fn(),
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

export const FilledForm: Story = {
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    // Fill out the form
    const emailInput = canvas.getByLabelText('Email');
    await userEvent.type(emailInput, '[email protected]', { delay: 50 });

    const passwordInput = canvas.getByLabelText('Password');
    await userEvent.type(passwordInput, 'password123', { delay: 50 });

    // Submit the form
    const submitButton = canvas.getByRole('button', { name: /submit/i });
    await userEvent.click(submitButton);

    // Assert the form was submitted
    await expect(args.onSubmit).toHaveBeenCalled();
  },
};

export const ValidationError: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Submit without filling
    const submitButton = canvas.getByRole('button', { name: /submit/i });
    await userEvent.click(submitButton);

    // Check for error messages
    await expect(canvas.getByText('Email is required')).toBeInTheDocument();
  },
};
MDX ドキュメント
{/* src/components/Button/Button.mdx */}
import { Meta, Story, Canvas, Controls, ArgTypes } from '@storybook/blocks';
import * as ButtonStories from './Button.stories';
import { Button } from './Button';

<Meta of={ButtonStories} />

# Button

The Button component is used to trigger actions or navigation.

## Import

```tsx
import { Button } from '@/components/Button';

使用方法

バリエーション

ボタンには、強調度の違いを伝える 4 つのバリエーションがあります。

プライマリー

ページの主要な行動喚起となる、主要なアクションに使用します。

セカンダリー

プライマリーアクションを補完する、二次的なアクションに使用します。

アウトライン

三次的なアクションや、視覚的な強調を抑えたい場合に使用します。

ゴースト

ナビゲーションや、非常に控えめなアクションに使用します。

サイズ

小 中 大

状態

無効
読み込み中

Props

アクセシビリティ

  • ボタンにはネイティブの <button> 要素を使用します
  • 読み込み状態はスクリーンリーダーに通知されます
  • フォーカス状態は明確に表示されます
  • 無効状態のボタンも適切な ARIA 属性を維持します

デザインガイドライン

  1. ボタンには具体的なテキストを使用します
  2. セクションごとにプライマリーボタンは 1 つにします
  3. ボタンのテキストは簡潔にします(2~4 語)
  4. アイコンは意味を補強するために使い、テキストの代わりにはしません

## Testing Integration

### Visual Regression with Chromatic

```typescript
// .storybook/main.ts
const config: StorybookConfig = {
  addons: [
    '@chromatic-com/storybook',
  ],
};
// package.json
{
  "scripts": {
    "chromatic": "chromatic --project-token=$CHROMATIC_PROJECT_TOKEN"
  }
}
テストランナー
npm install @storybook/test-runner -D
// .storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
import { injectAxe, checkA11y } from 'axe-playwright';

const config: TestRunnerConfig = {
  async preVisit(page) {
    await injectAxe(page);
  },
  async postVisit(page, context) {
    // Run accessibility tests
    const storyContext = await getStoryContext(page, context);
    if (!storyContext.parameters?.a11y?.disable) {
      await checkA11y(page, '#storybook-root', {
        detailedReport: true,
        detailedReportOptions: { html: true },
      });
    }
  },
};

export default config;
// package.json
{
  "scripts": {
    "test-storybook": "test-storybook",
    "test-storybook:ci": "test-storybook --ci"
  }
}

デザイントークンとの統合

// .storybook/preview.ts
import { ThemeProvider } from 'styled-components';
import { theme } from '../src/styles/theme';

const preview: Preview = {
  decorators: [
    (Story) => (
      <ThemeProvider theme={theme}>
        <Story />
      </ThemeProvider>
    ),
  ],
};
// src/styles/theme.ts (document in Storybook)
export const theme = {
  colors: {
    primary: {
      50: '#eff6ff',
      500: '#3b82f6',
      900: '#1e3a8a',
    },
  },
  spacing: {
    xs: '0.25rem',
    sm: '0.5rem',
    md: '1rem',
    lg: '1.5rem',
    xl: '2rem',
  },
  radii: {
    sm: '0.25rem',
    md: '0.375rem',
    lg: '0.5rem',
    full: '9999px',
  },
};

公開

静的エクスポート
// package.json
{
  "scripts": {
    "build-storybook": "storybook build",
    "storybook:serve": "npx http-server storybook-static"
  }
}
GitHub Pages
# .github/workflows/storybook.yml
name: Deploy Storybook

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - run: npm ci
      - run: npm run build-storybook

      - uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./storybook-static

ベストプラクティス

  1. ファイルごとに 1 つのコンポーネント: 整理しやすくします
  2. autodocs を使用する: ドキュメントを生成します
  3. Controls を追加する: インタラクティブに探索できるようにします
  4. a11y アドオンを含める: アクセシビリティをテストします
  5. play 関数を作成する: インタラクティブなテストを行います
  6. MDX でドキュメント化する: 内容が豊富なドキュメントを作成します
  7. デコレーターを使用する: コンテキストを統一します
  8. ビジュアルリグレッションテスト: UI の変更を検出します

出力チェックリスト

すべての Storybook セットアップには、次の項目を含めてください。

  • アドオンを含むメイン設定
  • グローバルデコレーターを含むプレビュー
  • CSF3 形式のストーリー
  • Autodocs の有効化
  • すべての props の Controls
  • アクセシビリティアドオン
  • ダークモードのサポート
  • ビューポートのプリセット
  • MDX ドキュメント
  • テストランナーのセットアップ
  • CI でのデプロイ
  • 静的ビルドスクリプト

コメント