コンテンツにスキップ

Storybookチートシート

Storybook - Build UIs in Isolation

Storybookは、UIコンポーネントとページを分離して構築するためのフロントエンドワークショップです。アプリ全体を実行せずに、到達が難しい状態やエッジケースを開発および共有するのに役立ちます。何千ものチームがUI開発、テスト、ドキュメント作成に使用しています。

[No text to translate] ```bash # Initialize Storybook in existing project npx storybook@latest init

Create new project with Storybook

npx create-react-app my-app cd my-app npx storybook@latest init

Start Storybook

npm run storybook

```bash
# Install Storybook CLI
npm install -g @storybook/cli

# Initialize in project
sb init

# Or specify framework
sb init --type react
sb init --type vue3
sb init --type angular
```[No text to translate - would need specific manual installation instructions]
```bash
# React
npx storybook@latest init --type react

# Vue 3
npx storybook@latest init --type vue3

# Angular
npx storybook@latest init --type angular

# Svelte
npx storybook@latest init --type svelte

# Web Components
npx storybook@latest init --type web-components

# HTML
npx storybook@latest init --type html
```[No text to translate - would need framework-specific setup details]

my-app/ ├── .storybook/ │ ├── main.js # Main configuration │ └── preview.js # Global settings ├── src/ │ ├── components/ │ │ ├── Button/ │ │ │ ├── Button.js │ │ │ ├── Button.css │ │ │ └── Button.stories.js │ │ └── Header/ │ │ ├── Header.js │ │ └── Header.stories.js │ └── stories/ # Example stories ├── package.json └── README.md

```javascript
// src/components/Button/Button.stories.js
import { Button } from './Button';

export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

export const Primary = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary = {
  args: {
    label: 'Button',
  },
};

export const Large = {
  args: {
    size: 'large',
    label: 'Button',
  },
};

export const Small = {
  args: {
    size: 'small',
    label: 'Button',
  },
};
```[No text to translate - would need basic story example]
```javascript
// src/components/Button/Button.js
import React from 'react';
import PropTypes from 'prop-types';
import './button.css';

export const Button = ({ primary = false, backgroundColor = null, size = 'medium', label, ...props }) => {
  const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
  return (
    <button
      type="button"
      className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
      style={{ backgroundColor }}
      {...props}
    >
      {label}
    </button>
  );
};

Button.propTypes = {
  primary: PropTypes.bool,
  backgroundColor: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  label: PropTypes.string.isRequired,
  onClick: PropTypes.func,
};

Button.defaultProps = {
  primary: false,
  onClick: undefined,
};
```[No text to translate - would need component example]
```bash
# Start development server
npm run storybook

# Build static Storybook
npm run build-storybook

# Serve built Storybook
npx http-server storybook-static

# Start with specific port
npm run storybook -- --port 9001

# Start in quiet mode
npm run storybook -- --quiet
```[No text to translate - would need Storybook running instructions]
```javascript
// Component.stories.js
import { Component } from './Component';

// Default export - story metadata
export default {
  title: 'Path/To/Component',
  component: Component,
  parameters: {
    // Story-level parameters
  },
  argTypes: {
    // Control definitions
  },
  args: {
    // Default args for all stories
  },
};

// Named exports - individual stories
export const Default = {};

export const WithProps = {
  args: {
    prop1: 'value1',
    prop2: 'value2',
  },
};

export const CustomStory = {
  render: (args) => <Component {...args} />,
  args: {
    // Story-specific args
  },
};
```[No text to translate - would need story structure explanation]
```javascript
// Organize stories with hierarchy
export default {
  title: 'Design System/Components/Button',
  // Creates: Design System > Components > Button
};

export default {
  title: 'Pages/Dashboard/UserProfile',
  // Creates: Pages > Dashboard > UserProfile
};

export default {
  title: 'Example/Button',
  // Creates: Example > Button
};
```[No text to translate - would need story naming guidelines]
```javascript
// stories/Forms.stories.js
import { Input } from '../components/Input';
import { Button } from '../components/Button';
import { Form } from '../components/Form';

export default {
  title: 'Forms/LoginForm',
};

export const LoginForm = () => (
  <Form>
    <Input type="email" placeholder="Email" />
    <Input type="password" placeholder="Password" />
    <Button primary>Login</Button>
  </Form>
);

export const SignupForm = () => (
  <Form>
    <Input placeholder="Full Name" />
    <Input type="email" placeholder="Email" />
    <Input type="password" placeholder="Password" />
    <Input type="password" placeholder="Confirm Password" />
    <Button primary>Sign Up</Button>
  </Form>
);
```[No text to translate - would need multiple components story description]
```javascript
export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    // Layout
    layout: 'centered', // 'centered' | 'fullscreen' | 'padded'
    
    // Backgrounds
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#333333' },
      ],
    },
    
    // Viewport
    viewport: {
      defaultViewport: 'mobile1',
    },
    
    // Documentation
    docs: {
      description: {
        component: 'Button component for user interactions',
      },
    },
  },
};
```[No text to translate - would need story parameters explanation]
```javascript
// Modern format with object syntax
export default {
  title: 'Example/Button',
  component: Button,
};

export const Primary = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary = {
  args: {
    primary: false,
    label: 'Button',
  },
};
```[No text to translate - would need CSF 3.0 details]
```javascript
// Function-based format
export default {
  title: 'Example/Button',
  component: Button,
};

const Template = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  primary: false,
  label: 'Button',
};
```[No text to translate - would need CSF 2.0 details]
```javascript
export const CustomRender = {
  render: (args) => {
    const [count, setCount] = React.useState(0);
    
    return (
      <div>
        <p>Count: {count}</p>
        <Button 
          {...args} 
          onClick={() => setCount(count + 1)}
        />
      </div>
    );
  },
  args: {
    label: 'Increment',
  },
};
```[No text to translate - would need custom render functions explanation]
```javascript
import { userEvent, within } from '@storybook/testing-library';

export const InteractiveExample = {
  args: {
    label: 'Click me',
  },
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    // Simulate user interaction
    await userEvent.click(button);
    
    // Add assertions if needed
    // expect(button).toHaveClass('clicked');
  },
};
```[No text to translate - would need play functions description]
```javascript
export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    // Text input
    label: {
      control: 'text',
      description: 'Button label text',
    },
    
    // Boolean
    primary: {
      control: 'boolean',
      description: 'Primary button style',
    },
    
    // Select dropdown
    size: {
      control: 'select',
      options: ['small', 'medium', 'large'],
      description: 'Button size',
    },
    
    // Radio buttons
    variant: {
      control: 'radio',
      options: ['primary', 'secondary', 'danger'],
    },
    
    // Color picker
    backgroundColor: {
      control: 'color',
    },
    
    // Number input
    borderRadius: {
      control: { type: 'number', min: 0, max: 50, step: 1 },
    },
    
    // Range slider
    opacity: {
      control: { type: 'range', min: 0, max: 1, step: 0.1 },
    },
    
    // Object editor
    style: {
      control: 'object',
    },
    
    // Array editor
    items: {
      control: 'object',
    },
    
    // Date picker
    createdAt: {
      control: 'date',
    },
    
    // Disable control
    onClick: {
      action: 'clicked',
      control: false,
    },
  },
};
```[No text to translate - would need ArgTypes configuration details]
```javascript
import { action } from '@storybook/addon-actions';

export default {
  title: 'Example/Button',
  component: Button,
  argTypes: {
    onClick: { action: 'clicked' },
    onMouseEnter: { action: 'mouse-enter' },
    onMouseLeave: { action: 'mouse-leave' },
  },
};

// Or in individual stories
export const WithActions = {
  args: {
    label: 'Button',
    onClick: action('button-click'),
    onMouseEnter: action('mouse-enter'),
  },
};

// Multiple actions
export const MultipleActions = {
  args: {
    label: 'Multi Action Button',
    onClick: (...args) => {
      action('primary-click')(...args);
      action('analytics-track')('button-clicked');
    },
  },
};
```[No text to translate - would need actions explanation]

Would you like me to provide placeholder translations for the sections without specific text, or do you want to provide the specific content you'd like translated?```javascript
export default {
  title: 'Example/Form',
  component: Form,
  argTypes: {
    // Conditional controls
    showAdvanced: {
      control: 'boolean',
    },
    advancedOptions: {
      control: 'object',
      if: { arg: 'showAdvanced', truthy: true },
    },
    
    // Custom control
    theme: {
      control: {
        type: 'select',
        labels: {
          light: 'Light Theme',
          dark: 'Dark Theme',
          auto: 'Auto (System)',
        },
      },
      options: ['light', 'dark', 'auto'],
    },
    
    // Multi-select
    features: {
      control: 'multi-select',
      options: ['feature1', 'feature2', 'feature3'],
    },
  },
};

アドオン

必須アドオン

// .storybook/main.js
module.exports = {
  addons: [
    '@storybook/addon-essentials', // Includes most common addons
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
    '@storybook/addon-design-tokens',
  ],
};

// Individual addons (if not using essentials)
module.exports = {
  addons: [
    '@storybook/addon-controls',
    '@storybook/addon-actions',
    '@storybook/addon-viewport',
    '@storybook/addon-backgrounds',
    '@storybook/addon-docs',
    '@storybook/addon-toolbars',
  ],
};

アクセシビリティアドオン

// .storybook/preview.js
export const parameters = {
  a11y: {
    // Optional selector to inspect
    element: '#storybook-root',
    config: {
      rules: [
        {
          // Disable specific rules
          id: 'color-contrast',
          enabled: false,
        },
      ],
    },
    // Show violations in action panel
    manual: true,
  },
};

// In stories
export const AccessibleButton = {
  args: {
    label: 'Accessible Button',
  },
  parameters: {
    a11y: {
      // Story-specific a11y config
      config: {
        rules: [
          { id: 'button-name', enabled: true },
        ],
      },
    },
  },
};

ビューポートアドオン

// .storybook/preview.js
export const parameters = {
  viewport: {
    viewports: {
      mobile: {
        name: 'Mobile',
        styles: {
          width: '375px',
          height: '667px',
        },
      },
      tablet: {
        name: 'Tablet',
        styles: {
          width: '768px',
          height: '1024px',
        },
      },
      desktop: {
        name: 'Desktop',
        styles: {
          width: '1024px',
          height: '768px',
        },
      },
    },
    defaultViewport: 'mobile',
  },
};

デザイントークンアドオン

// design-tokens.json
{
  "colors": {
    "primary": "#007bff",
    "secondary": "#6c757d",
    "success": "#28a745",
    "danger": "#dc3545"
  },
  "spacing": {
    "xs": "4px",
    "sm": "8px",
    "md": "16px",
    "lg": "24px",
    "xl": "32px"
  },
  "typography": {
    "fontFamily": {
      "primary": "Inter, sans-serif",
      "mono": "Monaco, monospace"
    },
    "fontSize": {
      "sm": "14px",
      "md": "16px",
      "lg": "18px",
      "xl": "24px"
    }
  }
}

// .storybook/main.js
module.exports = {
  addons: [
    {
      name: '@storybook/addon-design-tokens',
      options: {
        designTokenGlob: '**/design-tokens.json',
      },
    },
  ],
};

カスタムアドオン

// .storybook/addons/theme-switcher/register.js
import { addons, types } from '@storybook/addons';
import { ADDON_ID, TOOL_ID } from './constants';
import { Tool } from './Tool';

addons.register(ADDON_ID, () => {
  addons.add(TOOL_ID, {
    type: types.TOOL,
    title: 'Theme Switcher',
    render: Tool,
  });
});

// .storybook/addons/theme-switcher/Tool.js
import React from 'react';
import { useGlobals } from '@storybook/api';
import { IconButton } from '@storybook/components';

export const Tool = () => {
  const [globals, updateGlobals] = useGlobals();
  const theme = globals.theme || 'light';

  const toggleTheme = () => {
    updateGlobals({
      theme: theme === 'light' ? 'dark' : 'light',
    });
  };

  return (
    <IconButton
      key="theme-switcher"
      title="Toggle theme"
      onClick={toggleTheme}
    >
      {theme === 'light' ? '🌙' : '☀️'}
    </IconButton>
  );
};

設定

メイン設定

// .storybook/main.js
module.exports = {
  // Stories location
  stories: [
    '../src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
    '../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)',
  ],
  
  // Addons
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y',
  ],
  
  // Framework
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  
  // Features
  features: {
    buildStoriesJson: true,
    storyStoreV7: true,
  },
  
  // TypeScript
  typescript: {
    check: false,
    reactDocgen: 'react-docgen-typescript',
    reactDocgenTypescriptOptions: {
      shouldExtractLiteralValuesFromEnum: true,
      propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
    },
  },
  
  // Webpack customization
  webpackFinal: async (config) => {
    // Add custom webpack config
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
    });
    
    return config;
  },
  
  // Vite customization
  viteFinal: async (config) => {
    // Customize Vite config
    config.define = {
      ...config.define,
      global: 'globalThis',
    };
    
    return config;
  },
  
  // Documentation
  docs: {
    autodocs: 'tag',
  },
  
  // Static directories
  staticDirs: ['../public'],
};

プレビュー設定

// .storybook/preview.js
import '../src/index.css'; // Global styles

export const parameters = {
  // Actions
  actions: { argTypesRegex: '^on[A-Z].*' },
  
  // Controls
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
    expanded: true,
    sort: 'requiredFirst',
  },
  
  // Layout
  layout: 'centered',
  
  // Backgrounds
  backgrounds: {
    default: 'light',
    values: [
      { name: 'light', value: '#ffffff' },
      { name: 'dark', value: '#333333' },
      { name: 'gray', value: '#f8f9fa' },
    ],
  },
  
  // Viewport
  viewport: {
    viewports: {
      mobile1: {
        name: 'Small mobile',
        styles: { width: '320px', height: '568px' },
      },
      mobile2: {
        name: 'Large mobile',
        styles: { width: '414px', height: '896px' },
      },
      tablet: {
        name: 'Tablet',
        styles: { width: '768px', height: '1024px' },
      },
    },
  },
  
  // Documentation
  docs: {
    theme: themes.light,
    source: {
      state: 'open',
    },
  },
};

// Global decorators
export const decorators = [
  (Story) => (
    <div style={{ margin: '3em', fontFamily: 'Inter, sans-serif' }}>
      <Story />
    </div>
  ),
];

// Global args
export const args = {
  // Default args for all stories
};

// Global arg types
export const argTypes = {
  // Global arg types
};

環境変数

// .storybook/main.js
module.exports = {
  env: (config) => ({
    ...config,
    API_URL: 'https://api.example.com',
    FEATURE_FLAG: true,
  }),
  
  webpackFinal: async (config) => {
    config.plugins.push(
      new webpack.DefinePlugin({
        'process.env.STORYBOOK': JSON.stringify(true),
      })
    );
    return config;
  },
};

// In components
const apiUrl = process.env.STORYBOOK 
  ? 'https://api-mock.example.com' 
  : process.env.API_URL;

テーマ

カスタムテーマ

// .storybook/theme.js
import { create } from '@storybook/theming';

export default create({
  base: 'light', // 'light' | 'dark'
  
  // Brand
  brandTitle: 'My Company Storybook',
  brandUrl: 'https://example.com',
  brandImage: 'https://example.com/logo.svg',
  brandTarget: '_self',
  
  // Colors
  colorPrimary: '#FF4785',
  colorSecondary: '#029CFD',
  
  // UI
  appBg: '#F6F9FC',
  appContentBg: '#FFFFFF',
  appBorderColor: '#E6ECF0',
  appBorderRadius: 4,
  
  // Typography
  fontBase: '"Inter", sans-serif',
  fontCode: 'Monaco, monospace',
  
  // Text colors
  textColor: '#2E3438',
  textInverseColor: '#FFFFFF',
  textMutedColor: '#798186',
  
  // Toolbar
  barTextColor: '#798186',
  barSelectedColor: '#029CFD',
  barBg: '#FFFFFF',
  
  // Form
  inputBg: '#FFFFFF',
  inputBorder: '#E6ECF0',
  inputTextColor: '#2E3438',
  inputBorderRadius: 4,
});

// .storybook/manager.js
import { addons } from '@storybook/addons';
import theme from './theme';

addons.setConfig({
  theme,
});

ダークテーマ

// .storybook/dark-theme.js
import { create } from '@storybook/theming';

export default create({
  base: 'dark',
  
  brandTitle: 'My Company Storybook',
  brandUrl: 'https://example.com',
  brandImage: 'https://example.com/logo-white.svg',
  
  colorPrimary: '#FF4785',
  colorSecondary: '#029CFD',
  
  appBg: '#1A1A1A',
  appContentBg: '#2D2D2D',
  appBorderColor: '#404040',
  
  textColor: '#FFFFFF',
  textInverseColor: '#1A1A1A',
  textMutedColor: '#CCCCCC',
  
  barTextColor: '#CCCCCC',
  barSelectedColor: '#029CFD',
  barBg: '#2D2D2D',
  
  inputBg: '#404040',
  inputBorder: '#666666',
  inputTextColor: '#FFFFFF',
});

テーマ切り替え

// .storybook/preview.js
import { themes } from '@storybook/theming';

export const parameters = {
  docs: {
    theme: themes.light,
  },
  darkMode: {
    // Override the default dark theme
    dark: { ...themes.dark, appBg: 'black' },
    // Override the default light theme
    light: { ...themes.normal, appBg: 'white' },
    // Set the initial theme
    current: 'light',
    // Disable the addon for specific stories
    stylePreview: true,
  },
};

// Global decorator for theme switching
export const decorators = [
  (Story, context) => {
    const theme = context.globals.theme || 'light';
    
    return (
      <div className={`theme-${theme}`}>
        <Story />
      </div>
    );
  },
];

// Global types for theme switcher
export const globalTypes = {
  theme: {
    name: 'Theme',
    description: 'Global theme for components',
    defaultValue: 'light',
    toolbar: {
      icon: 'circlehollow',
      items: [
        { value: 'light', icon: 'sun', title: 'Light' },
        { value: 'dark', icon: 'moon', title: 'Dark' },
      ],
      showName: true,
    },
  },
};

テスト

ビジュアルテスト

// .storybook/test-runner.js
const { getStoryContext } = require('@storybook/test-runner');

module.exports = {
  async postRender(page, context) {
    const storyContext = await getStoryContext(page, context);
    
    // Skip visual tests for specific stories
    if (storyContext.parameters?.skipVisualTest) {
      return;
    }
    
    // Take screenshot
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotIdentifier: context.id,
    });
  },
};

// In stories
export const VisualTest = {
  args: {
    label: 'Visual Test Button',
  },
  parameters: {
    // Skip this story in visual tests
    skipVisualTest: true,
  },
};

インタラクションテスト

// Button.stories.js
import { userEvent, within, expect } from '@storybook/test';

export const InteractionTest = {
  args: {
    label: 'Click me',
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    
    // Find elements
    const button = canvas.getByRole('button', { name: /click me/i });
    
    // Simulate interactions
    await userEvent.click(button);
    
    // Assertions
    await expect(args.onClick).toHaveBeenCalled();
    
    // Check DOM changes
    await expect(button).toHaveClass('clicked');
    
    // Wait for async operations
    await canvas.findByText('Success!');
  },
};

export const FormInteraction = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Fill form
    const emailInput = canvas.getByLabelText(/email/i);
    const passwordInput = canvas.getByLabelText(/password/i);
    const submitButton = canvas.getByRole('button', { name: /submit/i });
    
    await userEvent.type(emailInput, 'user@example.com');
    await userEvent.type(passwordInput, 'password123');
    await userEvent.click(submitButton);
    
    // Check results
    await expect(canvas.getByText(/success/i)).toBeInTheDocument();
  },
};

ストーリーを使用した単体テスト

// Button.test.js
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';

const { Primary, Secondary, Large } = composeStories(stories);

describe('Button', () => {
  test('renders primary button', () => {
    render(<Primary />);
    expect(screen.getByRole('button')).toHaveClass('storybook-button--primary');
  });

  test('renders secondary button', () => {
    render(<Secondary />);
    expect(screen.getByRole('button')).not.toHaveClass('storybook-button--primary');
  });

  test('renders large button', () => {
    render(<Large />);
    expect(screen.getByRole('button')).toHaveClass('storybook-button--large');
  });
});

アクセシビリティテスト

// a11y.test.js
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';

expect.extend(toHaveNoViolations);

const { Primary, Secondary } = composeStories(stories);

describe('Button Accessibility', () => {
  test('Primary button should not have accessibility violations', async () => {
    const { container } = render(<Primary />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  test('Secondary button should not have accessibility violations', async () => {
    const { container } = render(<Secondary />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});

ドキュメンテーション

自動ドキュメンテーション

// Component with JSDoc
/**
 * Primary UI component for user interaction
 */
export const Button = ({
  /**
   * Is this the principal call to action on the page?
   */
  primary = false,
  /**
   * What background color to use
   */
  backgroundColor,
  /**
   * How large should the button be?
   */
  size = 'medium',
  /**
   * Button contents
   */
  label,
  /**
   * Optional click handler
   */
  onClick,
  ...props
}) => {
  // Component implementation
};

// Stories with auto-generated docs
export default {
  title: 'Example/Button',
  component: Button,
  tags: ['autodocs'], // Enable auto-documentation
  parameters: {
    docs: {
      description: {
        component: 'Button component for user interactions. Supports multiple sizes and variants.',
      },
    },
  },
};

カスタムドキュメンテーション

// Button.stories.js
export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    docs: {
      description: {
        component: `
# Button Component

The Button component is a fundamental UI element used throughout the application.

## Usage

\`\`\`jsx
import { Button } from './Button';

<Button primary onClick={handleClick}>
  Click me
</Button>
\`\`\`

## Design Guidelines

- Use primary buttons for main actions
- Use secondary buttons for supporting actions
- Ensure sufficient color contrast for accessibility
        `,
      },
    },
  },
};

export const Primary = {
  args: {
    primary: true,
    label: 'Button',
  },
  parameters: {
    docs: {
      description: {
        story: 'Primary buttons are used for the main call-to-action on a page.',
      },
    },
  },
};

MDXドキュメンテーション

<!-- Button.stories.mdx -->
import { Meta, Story, Canvas, ArgsTable, Description } from '@storybook/addon-docs';
import { Button } from './Button';

<Meta title="Example/Button" component={Button} />

# Button

<Description of={Button} />

## Examples

### Primary Button

<Canvas>
  <Story name="Primary" args={{ primary: true, label: 'Button' }}>
    {(args) => <Button {...args} />}
  </Story>
</Canvas>

### Secondary Button

<Canvas>
  <Story name="Secondary" args={{ label: 'Button' }}>
    {(args) => <Button {...args} />}
  </Story>
</Canvas>

## Props

<ArgsTable of={Button} />

## Usage Guidelines

- Use primary buttons sparingly, typically one per page
- Secondary buttons can be used multiple times
- Always provide meaningful labels
- Consider accessibility when choosing colors

## Code Example

```jsx
import { Button } from './Button';

function MyComponent() {
  return (
    <div>
      <Button primary onClick={() => alert('プライマリがクリックされました!')}>
        プライマリアクション
      </Button>
      <Button onClick={() => alert('セカンダリがクリックされました!')}>
        セカンダリアクション
      </Button>
    </div>
  );
}

### ドキュメンテーションページ

Would you like me to elaborate on any specific section or add more details to the translations?```javascript
// .storybook/main.js
module.exports = {
  stories: [
    '../src/**/*.stories.@(js|jsx|ts|tsx)',
    '../docs/**/*.stories.mdx', // Documentation stories
  ],
};

// docs/Introduction.stories.mdx
import { Meta } from '@storybook/addon-docs';

<Meta title="Introduction" />

# Design System

Welcome to our design system documentation.

## Getting Started

This Storybook contains all the components, patterns, and guidelines for our design system.

### Installation

``````bash
npm install @company/design-system

(This remains the same, as it’s a technical command)


### Usage

``````jsx
import { Button, Input, Card } from '@company/design-system';

(This also remains the same, as it’s a technical import statement)

Would you like me to proceed with the remaining sections, or do you want to provide the specific text for those sections?```

Principles

  1. Consistency - Maintain visual and functional consistency
  2. Accessibility - Ensure all components are accessible
  3. Performance - Optimize for speed and efficiency
  4. Flexibility - Support customization and theming

## Deployment

### Static Build
```bash
# Build Storybook for deployment
npm run build-storybook

# Output directory
ls storybook-static/

# Serve locally
npx http-server storybook-static

Netlify Deployment

# netlify.toml
[build]
  command = "npm run build-storybook"
  publish = "storybook-static"

[build.environment]
  NODE_VERSION = "16"

# Deploy
npm install -g netlify-cli
netlify deploy --prod --dir=storybook-static

Vercel Deployment

// vercel.json
{
  "buildCommand": "npm run build-storybook",
  "outputDirectory": "storybook-static",
  "framework": null
}

GitHub Pages

# .github/workflows/storybook.yml
name: Build and Deploy Storybook

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout
      uses: actions/checkout@v3
      
    - name: Setup Node
      uses: actions/setup-node@v3
      with:
        node-version: '16'
        cache: 'npm'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Build Storybook
      run: npm run build-storybook
      
    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./storybook-static

Docker Deployment

# Dockerfile
FROM node:16-alpine as builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build-storybook

FROM nginx:alpine
COPY --from=builder /app/storybook-static /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

AWS S3 + CloudFront

# Build and deploy to S3
npm run build-storybook
aws s3 sync storybook-static/ s3://my-storybook-bucket --delete
aws cloudfront create-invalidation --distribution-id ABCD1234 --paths "/*"

Advanced Features

Custom Webpack Configuration

// .storybook/main.js
const path = require('path');

module.exports = {
  webpackFinal: async (config) => {
    // Add alias
    config.resolve.alias = {
      ...config.resolve.alias,
      '@': path.resolve(__dirname, '../src'),
      '@components': path.resolve(__dirname, '../src/components'),
    };
    
    // Add custom loader
    config.module.rules.push({
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        {
          loader: 'sass-loader',
          options: {
            additionalData: `@import "@/styles/variables.scss";`,
          },
        },
      ],
    });
    
    // Add plugin
    config.plugins.push(
      new webpack.DefinePlugin({
        __VERSION__: JSON.stringify(process.env.npm_package_version),
      })
    );
    
    return config;
  },
};

Custom Babel Configuration

// .storybook/main.js
module.exports = {
  babel: async (options) => ({
    ...options,
    plugins: [
      ...options.plugins,
      ['babel-plugin-styled-components', { displayName: true }],
    ],
  }),
};

Environment-Specific Configuration

// .storybook/main.js
const isDevelopment = process.env.NODE_ENV === 'development';

module.exports = {
  addons: [
    '@storybook/addon-essentials',
    ...(isDevelopment ? ['@storybook/addon-a11y'] : []),
  ],
  
  features: {
    storyStoreV7: !isDevelopment, // Disable in development for faster builds
  },
  
  webpackFinal: async (config) => {
    if (isDevelopment) {
      // Development-specific webpack config
      config.optimization.minimize = false;
    }
    
    return config;
  },
};

Custom Manager UI

// .storybook/manager.js
import { addons } from '@storybook/addons';

addons.setConfig({
  // Show/hide panels
  showPanel: true,
  panelPosition: 'bottom', // 'bottom' | 'right'
  
  // Sidebar
  showNav: true,
  showToolbar: true,
  
  // Initial active panel
  selectedPanel: 'storybook/controls/panel',
  
  // Sidebar tree expansion
  initialActive: 'sidebar',
  sidebar: {
    showRoots: false,
    collapsedRoots: ['other'],
  },
  
  // Toolbar
  toolbar: {
    title: { hidden: false },
    zoom: { hidden: false },
    eject: { hidden: false },
    copy: { hidden: false },
    fullscreen: { hidden: false },
  },
});

Performance

Lazy Loading

// .storybook/main.js
module.exports = {
  features: {
    storyStoreV7: true, // Enable lazy loading
  },
  
  stories: [
    {
      directory: '../src/components',
      files: '**/*.stories.*',
      titlePrefix: 'Components',
    },
    {
      directory: '../src/pages',
      files: '**/*.stories.*',
      titlePrefix: 'Pages',
    },
  ],
};

Bundle Optimization

// .storybook/main.js
module.exports = {
  webpackFinal: async (config) => {
    // Code splitting
    config.optimization.splitChunks = {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    };
    
    // Tree shaking
    config.optimization.usedExports = true;
    config.optimization.sideEffects = false;
    
    return config;
  },
};

Memory Optimization

// .storybook/preview.js
export const parameters = {
  // Reduce memory usage
  options: {
    storySort: {
      method: 'alphabetical',
      order: ['Introduction', 'Components', 'Pages'],
      locales: 'en-US',
    },
  },
  
  // Disable source code addon for better performance
  docs: {
    source: {
      state: 'closed',
    },
  },
};

// Cleanup decorators
export const decorators = [
  (Story, context) => {
    // Cleanup on story change
    React.useEffect(() => {
      return () => {
        // Cleanup logic
      };
    }, [context.id]);
    
    return <Story />;
  },
];

Integration

Design Tools Integration

// Figma integration
export default {
  title: 'Example/Button',
  component: Button,
  parameters: {
    design: {
      type: 'figma',
      url: 'https://www.figma.com/file/ABC123/Design-System?node-id=123%3A456',
    },
  },
};

// Sketch integration
export const SketchExample = {
  parameters: {
    design: {
      type: 'sketch',
      url: 'https://sketch.cloud/s/abc123',
    },
  },
};

// Adobe XD integration
export const XDExample = {
  parameters: {
    design: {
      type: 'adobe-xd',
      url: 'https://xd.adobe.com/view/abc123',
    },
  },
};

Testing Integration

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  testPathIgnorePatterns: ['/node_modules/', '/storybook-static/'],
  
  // Test stories
  testMatch: [
    '<rootDir>/src/**/__tests__/**/*.(js|jsx|ts|tsx)',
    '<rootDir>/src/**/?(*.)(spec|test).(js|jsx|ts|tsx)',
    '<rootDir>/src/**/*.stories.test.(js|jsx|ts|tsx)',
  ],
};

// Chromatic integration
// .github/workflows/chromatic.yml
name: 'Chromatic'
on: push

jobs:
  chromatic-deployment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - run: yarn
      - uses: chromaui/action@v1
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

CI/CD Integration

# .github/workflows/storybook-tests.yml
name: Storybook Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'
      
      - run: npm ci
      - run: npm run build-storybook --quiet
      - run: npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
          "npx http-server storybook-static --port 6006 --silent" \
          "npx wait-on http://127.0.0.1:6006 && npm run test-storybook"

Troubleshooting

Common Issues

# Port already in use
npm run storybook -- --port 9001

# Clear cache
rm -rf node_modules/.cache/storybook

# Webpack issues
npm run storybook -- --no-manager-cache

# TypeScript issues
npm run storybook -- --no-dll

# Memory issues
NODE_OPTIONS="--max-old-space-size=4096" npm run storybook

Debug Mode

# Enable debug logging
DEBUG=storybook:* npm run storybook

# Webpack debug
npm run storybook -- --debug-webpack

# Verbose output
npm run storybook -- --verbose

パフォーマンスの問題

// .storybook/main.js
module.exports = {
  // Disable source maps in development
  webpackFinal: async (config) => {
    if (process.env.NODE_ENV === 'development') {
      config.devtool = false;
    }
    return config;
  },
  
  // Reduce bundle size
  features: {
    storyStoreV7: true,
    buildStoriesJson: true,
  },
};

ビルドの問題

# Clear all caches
rm -rf node_modules/.cache
rm -rf storybook-static
npm run build-storybook

# Check for conflicting dependencies
npm ls

# Update Storybook
npx storybook@latest upgrade

ベストプラクティス

ストーリーの構成

  • 階層的構造: 一貫性のある命名パターンを使用
  • 論理的なグループ化: 関連するコンポーネントをグループ化
  • 明確な命名: 説明的なストーリー名を使用
  • ドキュメンテーション: 包括的なドキュメンテーションを含める

コンポーネント開発

  • 分離: コンポーネントを分離して開発
  • Props インターフェース: 明確で一貫性のあるpropインターフェースを設計
  • デフォルト値: 適切なデフォルト値を提供
  • エラーハンドリング: エッジケースを丁寧に処理

テスト戦略

  • ビジュアルテスト: ビジュアル回帰テストを使用
  • インタラクションテスト: ユーザーインタラクションをテスト
  • アクセシビリティテスト: コンポーネントのアクセシビリティを確保
  • ユニットテスト: コンポーネントのロジックをテスト

パフォーマンス最適化

  • 遅延読み込み: ストーリーストア v7 を使用して遅延読み込み
  • バンドル分割: webpackの設定を最適化
  • メモリ管理: リソースを適切にクリーンアップ
  • ビルド最適化: ビルド時間を最小化

チームコラボレーション

  • デザインシステム: 一貫性のあるデザインシステムを維持
  • ドキュメンテーション: ドキュメンテーションを最新に保つ
  • コードレビュー: ストーリーとコンポーネントをレビュー
  • 自動化: テストとデプロイメントを自動化

概要

Storybookは、以下を可能にする現代のフロントエンド開発に不可欠なツールです:

  • コンポーネントの分離: UIコンポーネントを独立して開発およびテスト
  • ビジュアルドキュメンテーション: デザインシステムのライブドキュメンテーションを作成
  • インタラクティブ開発: リアルタイムのフィードバックでコンポーネントを構築
  • テスト統合: ビジュアル、インタラクション、アクセシビリティテスト
  • チームコラボレーション: チーム間でコンポーネントを共有
  • デザインシステム管理: 一貫性のあるUIパターンを維持

主な利点は、開発サイクルの高速化、コンポーネントの品質向上、チームコラボレーションの改善、包括的なドキュメンテーションです。Storybookはすべての主要フロントエンドフレームワークをサポートし、アドオンと設定によって広範なカスタマイズオプションを提供します。