storybook-story-writing by thebushidocollective/han
npx skills add https://github.com/thebushidocollective/han --skill storybook-story-writing使用组件故事格式 3 (CSF3) 编写结构良好、可维护的 Storybook 故事,以展示组件变体并确保一致的渲染效果。
CSF3 是 Storybook 的现代格式,使用对象语法来定义故事:
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
title: 'Components/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
backgroundColor: { control: 'color' },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
};
export const Secondary: Story = {
args: {
label: 'Button',
},
};
Component.stories.tsxPrimary、Secondary、、广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
LargeDisabledComponents/Forms/Input默认导出定义了所有故事的元数据:
const meta = {
title: 'Components/Button', // 导航路径
component: Button, // 组件引用
parameters: {}, // 故事级配置
tags: ['autodocs'], // 启用自动文档
argTypes: {}, // 控件类型
decorators: [], // 故事包装器
} satisfies Meta<typeof Button>;
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
type Story = StoryObj<typeof meta>;
为每个有意义的状态创建故事:
export const Default: Story = {
args: {
label: 'Click me',
},
};
export const Loading: Story = {
args: {
label: 'Loading...',
loading: true,
},
};
export const Disabled: Story = {
args: {
label: 'Disabled',
disabled: true,
},
};
export const WithIcon: Story = {
args: {
label: 'Download',
icon: 'download',
},
};
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
size: 'medium',
},
};
// 扩展现有故事
export const PrimaryLarge: Story = {
...Primary,
args: {
...Primary.args,
size: 'large',
},
};
export const WithTooltip: Story = {
args: {
label: 'Hover me',
tooltip: 'Click to submit',
},
parameters: {
docs: {
description: {
story: '悬停时显示工具提示以提供额外上下文。',
},
},
},
};
import { RouterDecorator } from '../decorators';
const meta = {
component: Navigation,
decorators: [
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
),
RouterDecorator,
],
} satisfies Meta<typeof Navigation>;
export const EmptyForm: Story = {
args: {
onSubmit: (data) => console.log(data),
},
};
export const PrefilledForm: Story = {
args: {
defaultValues: {
email: 'user@example.com',
name: 'John Doe',
},
},
};
export const WithValidationErrors: Story = {
args: {
errors: {
email: 'Invalid email format',
name: 'Name is required',
},
},
};
export const WithSidebar: Story = {
args: {
sidebar: <Sidebar items={sidebarItems} />,
children: <Content />,
},
parameters: {
layout: 'fullscreen',
},
};
const mockData = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
];
export const WithData: Story = {
args: {
items: mockData,
},
};
export const Empty: Story = {
args: {
items: [],
emptyMessage: 'No items found',
},
};
export const Mobile: Story = {
args: {
variant: 'mobile',
},
parameters: {
viewport: {
defaultViewport: 'mobile1',
},
},
};
export const Desktop: Story = {
args: {
variant: 'desktop',
},
parameters: {
viewport: {
defaultViewport: 'desktop',
},
},
};
// 错误 - 旧的 CSF2 格式
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = { label: 'Button' };
// 正确 - CSF3 格式
export const Primary: Story = {
args: { label: 'Button' },
};
// 错误
export const Complex: Story = {
render: (args) => {
const [state, setState] = useState(false);
useEffect(() => {
// 复杂的副作用
}, []);
return <Component {...args} />;
},
};
// 正确 - 将逻辑移到组件中或使用 play 函数
export const Complex: Story = {
args: { initialState: false },
};
// 错误
export const Story1: Story = {
args: { label: 'Button', size: 'medium', theme: 'light' },
};
export const Story2: Story = {
args: { label: 'Submit', size: 'medium', theme: 'light' },
};
// 正确 - 使用元数据级别的默认值
const meta = {
component: Button,
args: {
size: 'medium',
theme: 'light',
},
} satisfies Meta<typeof Button>;
export const Story1: Story = {
args: { label: 'Button' },
};
export const Story2: Story = {
args: { label: 'Submit' },
};
// 错误 - 缺少类型注解
export const Primary = {
args: { label: 'Button' },
};
// 正确 - 包含类型
export const Primary: Story = {
args: { label: 'Button' },
};
每周安装量
324
代码仓库
GitHub 星标数
122
首次出现时间
Jan 22, 2026
安全审计
已安装于
opencode287
codex280
gemini-cli280
github-copilot273
cursor256
kimi-cli239
Write well-structured, maintainable Storybook stories using Component Story Format 3 (CSF3) that showcase component variations and ensure consistent rendering.
CSF3 is the modern Storybook format that uses object syntax for stories:
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
title: 'Components/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
backgroundColor: { control: 'color' },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
};
export const Secondary: Story = {
args: {
label: 'Button',
},
};
Component.stories.tsxPrimary, Secondary, Large, DisabledComponents/Forms/InputThe default export defines metadata for all stories:
const meta = {
title: 'Components/Button', // Navigation path
component: Button, // Component reference
parameters: {}, // Story-level config
tags: ['autodocs'], // Enable auto-documentation
argTypes: {}, // Control types
decorators: [], // Wrappers for stories
} satisfies Meta<typeof Button>;
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
type Story = StoryObj<typeof meta>;
Create stories for each meaningful state:
export const Default: Story = {
args: {
label: 'Click me',
},
};
export const Loading: Story = {
args: {
label: 'Loading...',
loading: true,
},
};
export const Disabled: Story = {
args: {
label: 'Disabled',
disabled: true,
},
};
export const WithIcon: Story = {
args: {
label: 'Download',
icon: 'download',
},
};
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
size: 'medium',
},
};
// Extend existing stories
export const PrimaryLarge: Story = {
...Primary,
args: {
...Primary.args,
size: 'large',
},
};
export const WithTooltip: Story = {
args: {
label: 'Hover me',
tooltip: 'Click to submit',
},
parameters: {
docs: {
description: {
story: 'Shows a tooltip on hover to provide additional context.',
},
},
},
};
import { RouterDecorator } from '../decorators';
const meta = {
component: Navigation,
decorators: [
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
),
RouterDecorator,
],
} satisfies Meta<typeof Navigation>;
export const EmptyForm: Story = {
args: {
onSubmit: (data) => console.log(data),
},
};
export const PrefilledForm: Story = {
args: {
defaultValues: {
email: 'user@example.com',
name: 'John Doe',
},
},
};
export const WithValidationErrors: Story = {
args: {
errors: {
email: 'Invalid email format',
name: 'Name is required',
},
},
};
export const WithSidebar: Story = {
args: {
sidebar: <Sidebar items={sidebarItems} />,
children: <Content />,
},
parameters: {
layout: 'fullscreen',
},
};
const mockData = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
];
export const WithData: Story = {
args: {
items: mockData,
},
};
export const Empty: Story = {
args: {
items: [],
emptyMessage: 'No items found',
},
};
export const Mobile: Story = {
args: {
variant: 'mobile',
},
parameters: {
viewport: {
defaultViewport: 'mobile1',
},
},
};
export const Desktop: Story = {
args: {
variant: 'desktop',
},
parameters: {
viewport: {
defaultViewport: 'desktop',
},
},
};
// Bad - Old CSF2 format
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = { label: 'Button' };
// Good - CSF3 format
export const Primary: Story = {
args: { label: 'Button' },
};
// Bad
export const Complex: Story = {
render: (args) => {
const [state, setState] = useState(false);
useEffect(() => {
// Complex side effects
}, []);
return <Component {...args} />;
},
};
// Good - Move logic to component or use play functions
export const Complex: Story = {
args: { initialState: false },
};
// Bad
export const Story1: Story = {
args: { label: 'Button', size: 'medium', theme: 'light' },
};
export const Story2: Story = {
args: { label: 'Submit', size: 'medium', theme: 'light' },
};
// Good - Use meta-level defaults
const meta = {
component: Button,
args: {
size: 'medium',
theme: 'light',
},
} satisfies Meta<typeof Button>;
export const Story1: Story = {
args: { label: 'Button' },
};
export const Story2: Story = {
args: { label: 'Submit' },
};
// Bad - Missing type annotation
export const Primary = {
args: { label: 'Button' },
};
// Good - With type
export const Primary: Story = {
args: { label: 'Button' },
};
Weekly Installs
324
Repository
GitHub Stars
122
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode287
codex280
gemini-cli280
github-copilot273
cursor256
kimi-cli239
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
105,000 周安装
RAG检索增强生成系统实现指南:构建基于外部知识的AI问答与文档助手
313 周安装
使用 Packer 构建 Windows 镜像:AWS/Azure 平台指南与 WinRM 配置
313 周安装
Google Chat API 开发指南:Webhook 与交互式机器人集成教程
313 周安装
OpenAI Assistants API v2 使用指南与迁移方案 - 2026年弃用前必看
313 周安装
MCP CLI 脚本开发指南:为Claude Code构建高效本地工具与自动化脚本
313 周安装
Playwright MCP 开发指南:如何为微软 Playwright 添加 MCP 工具和 CLI 命令
313 周安装