--- name: storybook description: "Storybook 스토리 작성 및 CSF 3.0 베스트 프랙티스 스킬. 다음 상황에서 사용: (1) 새 스토리 파일(.stories.tsx, .stories.ts) 작성 시, (2) 기존 스토리 수정 시, (3) Args, Decorators, Parameters 설정 시, (4) Storybook 설정 파일(.storybook/) 작업 시, (5) 'story', 'stories', 'storybook', 'CSF' 키워드가 포함된 작업 시" license: MIT metadata: author: DaleStudy version: "1.0" --- # Storybook ## 모범 관례 ### 1. CSF 3.0 형식 사용 최신 Component Story Format 3.0 사용. 더 간결하고 타입 안전. ```tsx // ❌ 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', }, }; ``` ### 2. Args 기반 스토리 작성 컴포넌트 Props를 Args로 정의하여 Controls 패널에서 인터랙티브하게 조작 가능. ```tsx // ❌ 하드코딩된 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, }, }; ``` ### 3. 타입 안전한 Meta 정의 `satisfies` 키워드로 타입 체크와 타입 추론 동시 활용. ```tsx // ❌ 타입 추론 불가 const meta: Meta = { title: 'Components/Button', component: Button, }; // ✅ 타입 체크와 추론 모두 가능 const meta = { title: 'Components/Button', component: Button, argTypes: { variant: { control: 'select', options: ['primary', 'secondary', 'tertiary'], }, }, } satisfies Meta; export default meta; type Story = StoryObj; ``` ### 4. Decorators로 컨텍스트 제공 공통 래퍼나 Provider를 Decorator로 적용. ```tsx // 개별 스토리에 Decorator 적용 export const WithTheme: Story = { decorators: [ (Story) => ( ), ], }; // 모든 스토리에 Decorator 적용 const meta = { component: Button, decorators: [ (Story) => (
), ], } satisfies Meta; ``` ### 5. Parameters로 동작 커스터마이즈 ```tsx 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' }, }, }; ``` ### 6. ArgTypes로 Controls 세밀하게 제어 ```tsx 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; ``` ## 권장 스토리 구조 ```tsx 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 옵션 ```tsx 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 ```tsx 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 패턴 ```tsx // 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 설정 파일 ```typescript // .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; ``` ```typescript // .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; ```