storybook
123
总安装量
123
周安装量
#1921
全站排名
安装命令
npx skills add https://github.com/dalestudy/skills --skill storybook
Agent 安装分布
claude-code
105
opencode
77
codex
76
gemini-cli
75
antigravity
66
Skill 文档
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 = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'], // ìë 문ì ìì±
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Click me',
},
};
2. Args ê¸°ë° ì¤í 리 ìì±
ì»´í¬ëí¸ Props를 Argsë¡ ì ìíì¬ Controls í¨ëìì ì¸í°ëí°ë¸íê² ì¡°ì ê°ë¥.
기본ê°ì argsìì ì ì¸ (â argTypes.defaultValue ì¬ì© ê¸ì§). Metaì argsì 기본ê°ì ëë©´ Controls í¨ëìì ìëì¼ë¡ í´ë¹ ê°ì´ ì í
// â íëì½ë©ë Props
export const Disabled: Story = {
render: () => <Button disabled>Disabled</Button>,
};
// â
Args ì¬ì©
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled',
},
};
// â
Args ì¬ì¬ì© ë° ì¤ë²ë¼ì´ë
export const DisabledPrimary: Story = {
args: {
...Primary.args,
disabled: true,
},
};
3. íì ìì í Meta ì ì
satisfies í¤ìëë¡ íì
ì²´í¬ì íì
ì¶ë¡ ëì íì©.
// â íì
ì¶ë¡ ë¶ê°
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
};
// â
íì
ì²´í¬ì ì¶ë¡ 모ë ê°ë¥
const meta = {
title: 'Components/Button',
component: Button,
args: {
size: 'md',
variant: 'primary',
},
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary'],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
4. 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>;
5. 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' },
},
};
6. 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<typeof Button>;
ê¶ì¥ ì¤í 리 구조
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',
},
args: {
size: 'md',
variant: 'primary',
},
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary'],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// 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) => (
<ThemeProvider theme="custom">
<Story />
</ThemeProvider>
),
],
};
ì주 ì¬ì©ëë ArgTypes ìµì
ì°¸ê³ : 기본ê°ì
argTypes.defaultValueê° ìë **args**ìì ì ì¸
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) => (
<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;