Get the FREE Ultimate OpenClaw Setup Guide →

storybook

Scanned
npx machina-cli add skill DaleStudy/skills/storybook --openclaw
Files (1)
SKILL.md
10.1 KB

Storybook

모범 관례

1. CSF 3.0 형식 사용

최신 Component Story Format 3.0 사용. 더 간결하고 타입 안전.

// ❌ CSF 2.0 (구형)
export default {
  title: 'Components/Button',
  component: Button,
};

export const Primary = () => <Button variant="primary">Click me</Button>;

// ✅ CSF 3.0 (권장)
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta = {
  component: Button,
  tags: ['autodocs'], // 자동 문서 생성
  args: {
    variant: 'primary',
    children: 'Click me',
  },
} satisfies Meta<typeof Button>;

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

export const Primary: Story = {};

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

2. Args 기반 스토리 작성

컴포넌트 Props를 Args로 정의하여 Controls 패널에서 인터랙티브하게 조작 가능.

  • 기본값은 args에서 선언 (❌ argTypes.defaultValue 사용 금지). Meta의 args에 기본값을 두면 Controls 패널에서 자동으로 해당 값이 선택됨
  • 여러 스토리에 공통으로 필요한 args는 Meta(컴포넌트) 수준에서 선언하고, 개별 스토리에서는 차이점만 오버라이드
// ❌ 하드코딩된 Props
export const Disabled: Story = {
  render: () => <Button disabled>Disabled</Button>,
};

// ❌ 여러 스토리에서 같은 args 중복
export const Primary: Story = {
  args: { children: 'Click me', variant: 'primary' },
};
export const Secondary: Story = {
  args: { children: 'Click me', variant: 'secondary' },
};

// ✅ Meta에서 공통 args 선언, 스토리에서 차이점만 오버라이드
const meta = {
  component: Button,
  args: {
    children: 'Click me',
    variant: 'primary',
  },
} satisfies Meta<typeof Button>;

export const Primary: Story = {};

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

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

3. title 생략 — 파일 경로 기반 자동 추론

title을 문자열로 직접 명시하면 타입 안전하지 않고, 컴포넌트 이름/경로 변경 시 싱크가 깨지기 쉬움. Storybook은 파일 경로에서 사이드바 계층을 자동 추론하므로 title 생략.

// ❌ title 직접 명시 — 타입 안전하지 않고 싱크 깨짐 위험
const meta = {
  title: 'Components/Button',
  component: Button,
} satisfies Meta<typeof Button>;

// ✅ title 생략 — 파일 경로에서 자동 추론
const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

4. 타입 안전한 Meta 정의

satisfies 키워드로 타입 체크와 타입 추론 동시 활용.

// ❌ 타입 추론 불가
const meta: Meta<typeof Button> = {
  component: Button,
};

// ✅ 타입 체크와 추론 모두 가능
const meta = {
  component: Button,
  args: {
    size: 'md',
    variant: 'primary',
  },
} satisfies Meta<typeof Button>;

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

5. Decorators로 컨텍스트 제공

공통 래퍼나 Provider를 Decorator로 적용.

// 개별 스토리에 Decorator 적용
export const WithTheme: Story = {
  decorators: [
    (Story) => (
      <ThemeProvider theme="dark">
        <Story />
      </ThemeProvider>
    ),
  ],
};

// 모든 스토리에 Decorator 적용
const meta = {
  component: Button,
  decorators: [
    (Story) => (
      <div style={{ padding: '3rem' }}>
        <Story />
      </div>
    ),
  ],
} satisfies Meta<typeof Button>;

6. Parameters로 동작 커스터마이즈

const meta = {
  component: Button,
  parameters: {
    layout: 'centered', // 스토리를 중앙 정렬
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#000000' },
      ],
    },
  },
} satisfies Meta<typeof Button>;

// 개별 스토리에서 오버라이드
export const OnDark: Story = {
  parameters: {
    backgrounds: { default: 'dark' },
  },
};

7. ArgTypes — 자동 추론 우선, 수동 지정 최소화

Storybook은 컴포넌트 함수의 TypeScript 타입에서 최적의 argType을 자동 적용함. 수동으로 덮어쓰면 컴포넌트 타입 변경 시마다 argType 싱크를 맞춰야 하므로 타당한 이유 없이 argType을 직접 지정하지 않음.

수동 지정이 타당한 경우:

  • ReactNode 타입인데 Controls에서 텍스트 입력이 필요할 때 → control: 'text'
  • Compound pattern (컴포넌트를 여러 개 export) → argTypes로 명시
  • 특정 스토리에서 항상 고정되어야 하는 prop → control: false
// ❌ 불필요한 argType 수동 지정 — 타입 변경 시 싱크 깨짐
const meta = {
  component: Button,
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'tertiary'],
    },
    size: {
      control: 'radio',
      options: ['sm', 'md', 'lg'],
    },
    disabled: {
      control: 'boolean',
    },
  },
} satisfies Meta<typeof Button>;

// ✅ 자동 추론에 맡기고, 필요한 경우만 수동 지정
const meta = {
  component: Button,
  argTypes: {
    // ReactNode 타입이지만 텍스트 입력이 필요한 경우
    children: { control: 'text' },
  },
} satisfies Meta<typeof Button>;

// ✅ 특정 스토리에서 prop을 고정할 때 — control: false
export const Horizontal: Story = {
  args: { orientation: 'horizontal' },
  argTypes: {
    orientation: { control: false }, // 이 스토리에서는 항상 horizontal
  },
};

권장 스토리 구조

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

// 1. Meta 정의 — title 생략, 공통 args 선언, argTypes는 자동 추론에 위임
const meta = {
  component: Button,
  tags: ['autodocs'],
  parameters: {
    layout: 'centered',
  },
  args: {
    children: 'Button',
    size: 'md',
    variant: 'primary',
  },
} satisfies Meta<typeof Button>;

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

// 2. 기본 스토리 — Meta args를 그대로 사용
export const Primary: Story = {};

// 3. 변형 스토리들 — 차이점만 오버라이드
export const Secondary: Story = {
  args: {
    variant: 'secondary',
  },
};

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

// 4. prop 고정이 필요한 스토리 — control: false 사용
export const Horizontal: Story = {
  args: { orientation: 'horizontal' },
  argTypes: {
    orientation: { control: false },
  },
};

// 5. 복잡한 상태나 컨텍스트가 필요한 경우
export const WithCustomTheme: Story = {
  decorators: [
    (Story) => (
      <ThemeProvider theme="custom">
        <Story />
      </ThemeProvider>
    ),
  ],
};

ArgTypes 수동 지정이 필요한 경우 참고

원칙: 대부분의 argType은 Storybook이 컴포넌트 타입에서 자동 추론. 아래는 자동 추론이 부적절할 때만 사용.

기본값은 argTypes.defaultValue가 아닌 args에서 선언.

argTypes: {
  // ReactNode 타입이지만 텍스트 입력이 필요할 때
  children: { control: 'text' },

  // Range slider (자동 추론이 적절하지 않을 때)
  opacity: {
    control: { type: 'range', min: 0, max: 1, step: 0.1 },
  },

  // Action logger (이벤트 핸들러)
  onClick: { action: 'clicked' },

  // Control 비활성화 (특정 스토리에서 prop 고정)
  orientation: { control: false },
}

자주 사용되는 Parameters

parameters: {
  // 레이아웃 설정
  layout: 'centered' | 'fullscreen' | 'padded',

  // 배경 설정
  backgrounds: {
    default: 'light',
    values: [
      { name: 'light', value: '#ffffff' },
      { name: 'dark', value: '#333333' },
    ],
  },

  // Actions 패널 설정
  actions: {
    argTypesRegex: '^on[A-Z].*', // on으로 시작하는 Props 자동 감지
  },

  // Docs 설정
  docs: {
    description: {
      component: '버튼 컴포넌트 상세 설명',
    },
  },
}

Decorators 패턴

// 1. 스타일 래퍼
(Story) => (
  <div style={{ padding: '3rem' }}>
    <Story />
  </div>
)

// 2. Theme Provider
(Story) => (
  <ThemeProvider theme="dark">
    <Story />
  </ThemeProvider>
)

// 3. Router Provider (React Router 사용 시)
(Story) => (
  <MemoryRouter initialEntries={['/']}>
    <Story />
  </MemoryRouter>
)

// 4. 다국어 Provider
(Story) => (
  <I18nProvider locale="ko">
    <Story />
  </I18nProvider>
)

// 5. 전역 상태 Provider
(Story) => (
  <Provider store={mockStore}>
    <Story />
  </Provider>
)

파일 명명 규칙

Component.tsx           # 컴포넌트 구현
Component.stories.tsx   # 스토리 파일 (같은 디렉토리)
Component.test.tsx      # 테스트 파일

Storybook 설정 파일

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(ts|tsx)'],
  addons: [
    '@storybook/addon-essentials', // Controls, Actions, Docs 등
    '@storybook/addon-interactions', // Play functions
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
};

export default config;
// .storybook/preview.ts
import type { Preview } from '@storybook/react';

const preview: Preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
  // 모든 스토리에 적용될 전역 Decorators
  decorators: [
    (Story) => (
      <div style={{ fontFamily: 'Arial, sans-serif' }}>
        <Story />
      </div>
    ),
  ],
};

export default preview;

Source

git clone https://github.com/DaleStudy/skills/blob/main/skills/storybook/SKILL.mdView on GitHub

Overview

Storybook에서 CSF 3.0 형식으로 스토리를 구성하는 방법과 Args, Decorators, Parameters를 활용한 모범 사례를 제공합니다. 타이핑 안전성과 자동 문서를 높이고, title 자동 추론과 메타 정의의 최적화로 일관성을 유지합니다.

How This Skill Works

메타 객체를 중심으로 CSF 3.0 구조를 사용하고, Args를 통해 컴포넌트 프로퍼티를 제어합니다. 파일 경로로 title을 자동 추론하고, satisfies를 활용해 타입 안전성을 보장합니다. 필요 시 Decorator와 Parameters로 컨텍스트와 런타임 동작을 주입합니다.

When to Use It

  • 새 스토리 파일(.stories.tsx, .stories.ts) 작성 시 CSF 3.0 형식을 적용한다.
  • 기존 스토리 수정 시 메타에 공통 Args를 선언하고 스토리별 차이점만 오버라이드한다.
  • Args, Decorators, Parameters를 설정해 인터랙티브한 제어와 런타임 동작을 커스터마이즈한다.
  • Storybook 설정 파일(.storybook/)를 구성해 자동 문서화와 타입 안전성을 강화한다.
  • 'story', 'stories', 'storybook', 'CSF' 키워드가 포함된 작업 시 CSF 3.0 컨벤션을 적용한다.

Quick Start

  1. Step 1: CSF 3.0 메타를 정의하고 title은 파일 경로로 자동 추론되도록 구성한다.
  2. Step 2: Meta.args에 기본값을 설정하고, 각 스토리는 필요한 차이점만 오버라이드한다.
  3. Step 3: 필요 시 Decorators와 Parameters를 추가하고 자동 문서화를 활용한다.

Best Practices

  • CSF 3.0 형식을 사용해 코드 간결성과 타입 안전성을 확보한다.
  • Args 기반 스토리 작성을 우선하고, 메타에서 공통 args를 선언해 중복을 줄인다.
  • title을 파일 경로로 자동 추론하도록 두고, 수동 명시는 피한다.
  • 타입 안전한 메타 정의를 위해 satisfies로 타입 체크와 추론을 함께 활용한다.
  • Decorators와 Parameters를 적절히 사용해 컨텍스트 제공 및 동작 커스터마이즈를 일관되게 관리한다.

Example Use Cases

  • CSF 3.0로 Button 컴포넌트의 스토리 작성 예시를 통해 기본 구조를 구현한다.
  • 메타에 공통 args를 선언하고 Primary/Secondary 같은 스토리에서 차이점만 오버라이드한다.
  • 타입 안전한 메타 정의를 사용해 Button 스토리의 타입 추론과 체크를 동시에 달성한다.
  • WithTheme Decorator를 적용해 전체 스토리에 ThemeProvider 컨텍스트를 주입한다.
  • Parameters로 layout와 backgrounds를 설정해 스토리의 렌더링 환경을 커스터마이즈한다.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers