Storybook 모범 관례 1. CSF 3.0 형식 사용
최신 Component Story Format 3.0 사용. 더 간결하고 타입 안전.
// ❌ CSF 2.0 (구형) export default { title: 'Components/Button', component: Button, };
export const Primary = () => ;
// ✅ CSF 3.0 (권장) import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button';
const meta = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'], // 자동 문서 생성
} satisfies Meta
export default meta;
type Story = StoryObj
export const Primary: Story = { args: { variant: 'primary', children: 'Click me', }, };
- Args 기반 스토리 작성
컴포넌트 Props를 Args로 정의하여 Controls 패널에서 인터랙티브하게 조작 가능.
// ❌ 하드코딩된 Props export const Disabled: Story = { render: () => , };
// ✅ Args 사용 export const Disabled: Story = { args: { disabled: true, children: 'Disabled', }, };
// ✅ Args 재사용 및 오버라이드 export const DisabledPrimary: Story = { args: { ...Primary.args, disabled: true, }, };
- 타입 안전한 Meta 정의
satisfies 키워드로 타입 체크와 타입 추론 동시 활용.
// ❌ 타입 추론 불가
const meta: Meta
// ✅ 타입 체크와 추론 모두 가능
const meta = {
title: 'Components/Button',
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary'],
},
},
} satisfies Meta
export default meta;
type Story = StoryObj
- Decorators로 컨텍스트 제공
공통 래퍼나 Provider를 Decorator로 적용.
// 개별 스토리에 Decorator 적용
export const WithTheme: Story = {
decorators: [
(Story) => (
// 모든 스토리에 Decorator 적용 const meta = { component: Button, decorators: [ (Story) => (
- Parameters로 동작 커스터마이즈
const meta = {
component: Button,
parameters: {
layout: 'centered', // 스토리를 중앙 정렬
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#000000' },
],
},
},
} satisfies Meta
;
// 개별 스토리에서 오버라이드 export const OnDark: Story = { parameters: { backgrounds: { default: 'dark' }, }, };
- ArgTypes로 Controls 세밀하게 제어
const meta = {
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary'],
description: '버튼 스타일 변형',
},
size: {
control: 'radio',
options: ['sm', 'md', 'lg'],
},
onClick: {
action: 'clicked', // Actions 패널에 표시
},
children: {
control: 'text',
},
disabled: {
control: 'boolean',
},
},
} satisfies Meta
;
권장 스토리 구조 import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button';
// 1. Meta 정의
const meta = {
title: 'Components/Button', // 사이드바 계층 구조
component: Button,
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary'],
},
},
} satisfies Meta
export default meta;
type Story = StoryObj
// 2. 기본 스토리 export const Primary: Story = { args: { variant: 'primary', children: 'Button', }, };
// 3. 변형 스토리들 export const Secondary: Story = { args: { ...Primary.args, variant: 'secondary', }, };
export const Disabled: Story = { args: { ...Primary.args, disabled: true, }, };
// 4. 복잡한 상태나 컨텍스트가 필요한 경우
export const WithCustomTheme: Story = {
args: Primary.args,
decorators: [
(Story) => (
자주 사용되는 ArgTypes 옵션 argTypes: { // Select dropdown variant: { control: 'select', options: ['primary', 'secondary'], },
// Radio buttons size: { control: 'radio', options: ['sm', 'md', 'lg'], },
// Boolean toggle disabled: { control: 'boolean', },
// Text input label: { control: 'text', },
// Number input count: { control: 'number', },
// Range slider opacity: { control: { type: 'range', min: 0, max: 1, step: 0.1 }, },
// Color picker backgroundColor: { control: 'color', },
// Date picker date: { control: 'date', },
// Action logger (이벤트 핸들러) onClick: { action: 'clicked', },
// Control 비활성화 className: { 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) => (
)
// 2. Theme Provider
(Story) => (
// 3. Router Provider (React Router 사용 시)
(Story) => (
// 4. 다국어 Provider
(Story) => (
// 5. 전역 상태 Provider
(Story) => (
파일 명명 규칙 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) => (
export default preview;