agent-storybook
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStorybook Skill
Storybook技能
Expert in Storybook for Angular 21+ - component-driven development, visual testing, and living design system documentation.
精通适用于Angular 21+的Storybook——组件驱动开发、视觉测试和活态设计系统文档。
When to Use This Skill
何时使用此技能
Use this skill when:
- Creating or updating component stories
- Setting up Storybook for a project
- Creating design system documentation stories
- Visual testing and accessibility testing
- Troubleshooting Storybook issues
- Configuring Storybook addons
- Writing component documentation
在以下场景使用此技能:
- 创建或更新组件故事
- 为项目搭建Storybook
- 创建设计系统文档故事
- 视觉测试与无障碍测试
- 排查Storybook问题
- 配置Storybook插件
- 编写组件文档
Core Principles
核心原则
Component-Driven Development
组件驱动开发
- Build components in isolation before integrating into app
- Test all component states and variants
- Document component API (inputs, outputs, methods)
- Use Storybook as the single source of truth for UI components
- 在集成到应用前独立构建组件
- 测试组件的所有状态和变体
- 记录组件API(输入、输出、方法)
- 将Storybook作为UI组件的唯一可信来源
Story Best Practices
故事最佳实践
- One story file per component ()
.stories.ts - Export multiple story variants showing different states
- Use descriptive story names (Primary, Disabled, Loading, Error, etc.)
- Include all edge cases and states
- Add accessibility testing to every story
- 每个组件对应一个故事文件()
.stories.ts - 导出多个展示不同状态的故事变体
- 使用描述性的故事名称(Primary、Disabled、Loading、Error等)
- 覆盖所有边缘情况和状态
- 为每个故事添加无障碍测试
Project Structure
项目结构
Standard Storybook Setup
标准Storybook配置
apps/frontend/
├── .storybook/
│ ├── main.ts # Main configuration
│ ├── preview.ts # Global decorators & parameters
│ └── manager.ts # UI customization (optional)
├── src/
│ └── app/
│ ├── components/
│ │ └── button/
│ │ ├── button.component.ts
│ │ └── button.stories.ts
│ └── pages/
│ └── home/
│ ├── home.component.ts
│ └── home.stories.tsapps/frontend/
├── .storybook/
│ ├── main.ts # 主配置文件
│ ├── preview.ts # 全局装饰器和参数
│ └── manager.ts # UI自定义(可选)
├── src/
│ └── app/
│ ├── components/
│ │ └── button/
│ │ ├── button.component.ts
│ │ └── button.stories.ts
│ └── pages/
│ └── home/
│ ├── home.component.ts
│ └── home.stories.tsStory Naming Convention
故事命名规范
Hierarchical Structure
层级结构
Use separator for organization:
/Components:
- - Basic button
Components/Button - - Nested form input
Components/Forms/Input - - Card components
Components/Cards/TaskCard
Pages:
- - Page-level story
Pages/Home - - Nested page
Pages/Auth/Login
Design System:
- - Color palette
Design System/Colors - - Font styles
Design System/Typography - - Spacing tokens
Design System/Spacing
Examples:
- - Complete form example
Examples/Forms/LoginForm - - Layout example
Examples/Layouts/Dashboard
使用分隔符进行组织:
/组件:
- - 基础按钮
Components/Button - - 嵌套表单输入框
Components/Forms/Input - - 卡片组件
Components/Cards/TaskCard
页面:
- - 页面级故事
Pages/Home - - 嵌套页面
Pages/Auth/Login
设计系统:
- - 调色板
Design System/Colors - - 字体样式
Design System/Typography - - 间距标记
Design System/Spacing
示例:
- - 完整表单示例
Examples/Forms/LoginForm - - 布局示例
Examples/Layouts/Dashboard
Story Template (Angular 21+ Standalone)
故事模板(Angular 21+ 独立组件)
Basic Component Story
基础组件故事
typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { ButtonComponent } from './button.component';
const meta: Meta<ButtonComponent> = {
title: 'Components/Button',
component: ButtonComponent,
tags: ['autodocs'], // Automatic documentation
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
description: 'Button visual style',
},
size: {
control: 'select',
options: ['small', 'medium', 'large'],
},
disabled: {
control: 'boolean',
},
loading: {
control: 'boolean',
},
},
};
export default meta;
type Story = StoryObj<ButtonComponent>;
// Primary state
export const Primary: Story = {
args: {
label: 'Click me',
variant: 'primary',
size: 'medium',
},
};
// Secondary state
export const Secondary: Story = {
args: {
label: 'Click me',
variant: 'secondary',
},
};
// Disabled state
export const Disabled: Story = {
args: {
label: 'Click me',
disabled: true,
},
};
// Loading state
export const Loading: Story = {
args: {
label: 'Please wait...',
loading: true,
},
};
// All sizes comparison
export const AllSizes: Story = {
render: (args) => ({
props: args,
template: `
<div style="display: flex; gap: 1rem; align-items: center;">
<app-button size="small" [label]="label"></app-button>
<app-button size="medium" [label]="label"></app-button>
<app-button size="large" [label]="label"></app-button>
</div>
`,
}),
args: {
label: 'Click me',
},
};typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { ButtonComponent } from './button.component';
const meta: Meta<ButtonComponent> = {
title: 'Components/Button',
component: ButtonComponent,
tags: ['autodocs'], // 自动生成文档
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
description: '按钮视觉样式',
},
size: {
control: 'select',
options: ['small', 'medium', 'large'],
},
disabled: {
control: 'boolean',
},
loading: {
control: 'boolean',
},
},
};
export default meta;
type Story = StoryObj<ButtonComponent>;
// 主状态
export const Primary: Story = {
args: {
label: '点击我',
variant: 'primary',
size: 'medium',
},
};
// 次要状态
export const Secondary: Story = {
args: {
label: '点击我',
variant: 'secondary',
},
};
// 禁用状态
export const Disabled: Story = {
args: {
label: '点击我',
disabled: true,
},
};
// 加载状态
export const Loading: Story = {
args: {
label: '请稍候...',
loading: true,
},
};
// 全尺寸对比
export const AllSizes: Story = {
render: (args) => ({
props: args,
template: `
<div style="display: flex; gap: 1rem; align-items: center;">
<app-button size="small" [label]="label"></app-button>
<app-button size="medium" [label]="label"></app-button>
<app-button size="large" [label]="label"></app-button>
</div>
`,
}),
args: {
label: '点击我',
},
};Component with Services/Dependencies
含服务/依赖的组件
typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { applicationConfig } from '@storybook/angular';
import { provideHttpClient } from '@angular/common/http';
import { TaskCardComponent } from './task-card.component';
const meta: Meta<TaskCardComponent> = {
title: 'Components/Cards/TaskCard',
component: TaskCardComponent,
tags: ['autodocs'],
decorators: [
applicationConfig({
providers: [
provideHttpClient(),
// Add other providers here
],
}),
],
};
export default meta;
type Story = StoryObj<TaskCardComponent>;
export const Default: Story = {
args: {
task: {
id: '1',
title: 'Clean bathroom',
assignee: 'Alex',
points: 10,
completed: false,
},
},
};
export const Completed: Story = {
args: {
task: {
id: '2',
title: 'Take out trash',
assignee: 'Sarah',
points: 5,
completed: true,
},
},
};typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { applicationConfig } from '@storybook/angular';
import { provideHttpClient } from '@angular/common/http';
import { TaskCardComponent } from './task-card.component';
const meta: Meta<TaskCardComponent> = {
title: 'Components/Cards/TaskCard',
component: TaskCardComponent,
tags: ['autodocs'],
decorators: [
applicationConfig({
providers: [
provideHttpClient(),
// 在此添加其他提供者
],
}),
],
};
export default meta;
type Story = StoryObj<TaskCardComponent>;
export const Default: Story = {
args: {
task: {
id: '1',
title: '打扫浴室',
assignee: 'Alex',
points: 10,
completed: false,
},
},
};
export const Completed: Story = {
args: {
task: {
id: '2',
title: '倒垃圾',
assignee: 'Sarah',
points: 5,
completed: true,
},
},
};Complex Layout Story
复杂布局故事
typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { HomeComponent } from './home.component';
import { TaskCardComponent } from '../../components/task-card/task-card.component';
import { StatCardComponent } from '../../components/stat-card/stat-card.component';
const meta: Meta<HomeComponent> = {
title: 'Pages/Home',
component: HomeComponent,
tags: ['autodocs'],
parameters: {
layout: 'fullscreen', // Use full viewport
},
decorators: [
applicationConfig({
providers: [
/* ... */
],
}),
],
};
export default meta;
type Story = StoryObj<HomeComponent>;
export const Default: Story = {};
export const WithManyTasks: Story = {
args: {
tasks: [
{ id: '1', title: 'Task 1' /* ... */ },
{ id: '2', title: 'Task 2' /* ... */ },
{ id: '3', title: 'Task 3' /* ... */ },
],
},
};
export const EmptyState: Story = {
args: {
tasks: [],
},
};typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { HomeComponent } from './home.component';
import { TaskCardComponent } from '../../components/task-card/task-card.component';
import { StatCardComponent } from '../../components/stat-card/stat-card.component';
const meta: Meta<HomeComponent> = {
title: 'Pages/Home',
component: HomeComponent,
tags: ['autodocs'],
parameters: {
layout: 'fullscreen', // 使用完整视口
},
decorators: [
applicationConfig({
providers: [
/* ... */
],
}),
],
};
export default meta;
type Story = StoryObj<HomeComponent>;
export const Default: Story = {};
export const WithManyTasks: Story = {
args: {
tasks: [
{ id: '1', title: '任务1' /* ... */ },
{ id: '2', title: '任务2' /* ... */ },
{ id: '3', title: '任务3' /* ... */ },
],
},
};
export const EmptyState: Story = {
args: {
tasks: [],
},
};Modal Component Story
模态框组件故事
CRITICAL: All modal components MUST have Storybook stories.
Modals require special testing because they have multiple interaction points (backdrop, ESC key, close button) that can fail silently.
typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { signal } from '@angular/core';
import { AddChildModal } from './add-child-modal';
const meta: Meta<AddChildModal> = {
title: 'Components/Modals/AddChildModal',
component: AddChildModal,
tags: ['autodocs'],
argTypes: {
open: { control: 'boolean' },
},
// Decorators for modal positioning
decorators: [
(story) => ({
template: `
<div style="min-height: 600px; position: relative;">
<story />
</div>
`,
}),
],
};
export default meta;
type Story = StoryObj<AddChildModal>;
// Default: Modal open
export const Default: Story = {
args: {
open: true,
},
};
// Closed state
export const Closed: Story = {
args: {
open: false,
},
parameters: {
docs: {
description: {
story: 'Modal in closed state (not visible)',
},
},
},
};
// With validation errors
export const WithValidationErrors: Story = {
args: {
open: true,
},
play: async ({ canvasElement }) => {
// Simulate invalid form state for visual testing
const canvas = within(canvasElement);
const submitButton = await canvas.findByRole('button', { name: /add child/i });
await userEvent.click(submitButton);
// Form validation errors should now be visible
},
};Modal Story Testing Checklist:
Test these interactions in Storybook:
- Open/Close: Toggle control
open - Close Button: Click X button
- Backdrop Click: Click outside modal
- ESC Key: Press Escape
- Form Validation: Submit empty form
- Loading State: Test submitting state
- Error State: Test error messages
- Accessibility: No violations in a11y addon
Common Modal Story Mistakes:
❌ Wrong: No Storybook story (bugs discovered in production)
✅ Correct: Story created BEFORE integrating into app
❌ Wrong: Only testing open state
✅ Correct: Testing open, closed, loading, error states
❌ Wrong: Not testing close interactions
✅ Correct: Manually test X button, backdrop, ESC in Storybook
See for Modal API usage.
.claude/skills/modal-components/SKILL.md重要提示:所有模态框组件必须配备Storybook故事。
模态框需要特殊测试,因为它们有多个交互点(背景遮罩、ESC键、关闭按钮),这些点可能会无声地失效。
typescript
import type { Meta, StoryObj } from '@storybook/angular';
import { signal } from '@angular/core';
import { AddChildModal } from './add-child-modal';
const meta: Meta<AddChildModal> = {
title: 'Components/Modals/AddChildModal',
component: AddChildModal,
tags: ['autodocs'],
argTypes: {
open: { control: 'boolean' },
},
// 模态框定位装饰器
decorators: [
(story) => ({
template: `
<div style="min-height: 600px; position: relative;">
<story />
</div>
`,
}),
],
};
export default meta;
type Story = StoryObj<AddChildModal>;
// 默认:模态框打开
export const Default: Story = {
args: {
open: true,
},
};
// 关闭状态
export const Closed: Story = {
args: {
open: false,
},
parameters: {
docs: {
description: {
story: '模态框处于关闭状态(不可见)',
},
},
},
};
// 含验证错误
export const WithValidationErrors: Story = {
args: {
open: true,
},
play: async ({ canvasElement }) => {
// 为视觉测试模拟无效表单状态
const canvas = within(canvasElement);
const submitButton = await canvas.findByRole('button', { name: /添加子项/i });
await userEvent.click(submitButton);
// 表单验证错误现在应该可见
},
};模态框故事测试清单:
在Storybook中测试以下交互:
- 打开/关闭:切换控制器
open - 关闭按钮:点击X按钮
- 背景遮罩点击:点击模态框外部
- ESC键:按下Escape键
- 表单验证:提交空表单
- 加载状态:测试提交状态
- 错误状态:测试错误消息
- 无障碍:a11y插件中无违规项
常见模态框故事错误:
❌ 错误做法: 没有Storybook故事(在生产环境中才发现bug)
✅ 正确做法: 在集成到应用前创建故事
❌ 错误做法: 仅测试打开状态
✅ 正确做法: 测试打开、关闭、加载、错误状态
❌ 错误做法: 未测试关闭交互
✅ 正确做法: 在Storybook中手动测试X按钮、背景遮罩、ESC键
查看 获取模态框API用法。
.claude/skills/modal-components/SKILL.mdDesign System Documentation
设计系统文档
Color Palette Story
调色板故事
typescript
import type { Meta, StoryObj } from '@storybook/angular';
const meta: Meta = {
title: 'Design System/Colors',
tags: ['autodocs'],
};
export default meta;
export const Primary: StoryObj = {
render: () => ({
template: `
<div style="display: grid; gap: 1rem;">
<div style="display: flex; align-items: center; gap: 1rem;">
<div style="width: 100px; height: 100px; background: var(--color-primary); border-radius: 8px;"></div>
<div>
<h3>Primary</h3>
<p>--color-primary: #6366F1</p>
</div>
</div>
<div style="display: flex; align-items: center; gap: 1rem;">
<div style="width: 100px; height: 100px; background: var(--color-secondary); border-radius: 8px;"></div>
<div>
<h3>Secondary</h3>
<p>--color-secondary: #EC4899</p>
</div>
</div>
<!-- Add more colors -->
</div>
`,
}),
};
export const AllColors: StoryObj = {
render: () => ({
template: `
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">
<div style="background: var(--color-primary); padding: 2rem; border-radius: 8px; color: white;">
<div style="font-weight: bold;">Primary</div>
<div style="font-size: 0.875rem;">#6366F1</div>
</div>
<div style="background: var(--color-secondary); padding: 2rem; border-radius: 8px; color: white;">
<div style="font-weight: bold;">Secondary</div>
<div style="font-size: 0.875rem;">#EC4899</div>
</div>
<!-- Add all design system colors -->
</div>
`,
}),
};typescript
import type { Meta, StoryObj } from '@storybook/angular';
const meta: Meta = {
title: 'Design System/Colors',
tags: ['autodocs'],
};
export default meta;
export const Primary: StoryObj = {
render: () => ({
template: `
<div style="display: grid; gap: 1rem;">
<div style="display: flex; align-items: center; gap: 1rem;">
<div style="width: 100px; height: 100px; background: var(--color-primary); border-radius: 8px;"></div>
<div>
<h3>主色调</h3>
<p>--color-primary: #6366F1</p>
</div>
</div>
<div style="display: flex; align-items: center; gap: 1rem;">
<div style="width: 100px; height: 100px; background: var(--color-secondary); border-radius: 8px;"></div>
<div>
<h3>次要色调</h3>
<p>--color-secondary: #EC4899</p>
</div>
</div>
<!-- 添加更多颜色 -->
</div>
`,
}),
};
export const AllColors: StoryObj = {
render: () => ({
template: `
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">
<div style="background: var(--color-primary); padding: 2rem; border-radius: 8px; color: white;">
<div style="font-weight: bold;">主色调</div>
<div style="font-size: 0.875rem;">#6366F1</div>
</div>
<div style="background: var(--color-secondary); padding: 2rem; border-radius: 8px; color: white;">
<div style="font-weight: bold;">次要色调</div>
<div style="font-size: 0.875rem;">#EC4899</div>
</div>
<!-- 添加所有设计系统颜色 -->
</div>
`,
}),
};Typography Story
排版故事
typescript
export const Typography: StoryObj = {
render: () => ({
template: `
<div style="font-family: var(--font-body);">
<h1 style="font-family: var(--font-heading); font-size: 48px; font-weight: 700;">
Heading 1 - Fredoka
</h1>
<h2 style="font-family: var(--font-heading); font-size: 36px; font-weight: 600;">
Heading 2 - Fredoka
</h2>
<p style="font-size: 16px;">
Body text - Outfit Regular
</p>
<p style="font-size: 16px; font-weight: 600;">
Body text - Outfit Semibold
</p>
</div>
`,
}),
};typescript
export const Typography: StoryObj = {
render: () => ({
template: `
<div style="font-family: var(--font-body);">
<h1 style="font-family: var(--font-heading); font-size: 48px; font-weight: 700;">
标题1 - Fredoka
</h1>
<h2 style="font-family: var(--font-heading); font-size: 36px; font-weight: 600;">
标题2 - Fredoka
</h2>
<p style="font-size: 16px;">
正文文本 - Outfit Regular
</p>
<p style="font-size: 16px; font-weight: 600;">
正文文本 - Outfit Semibold
</p>
</div>
`,
}),
};Storybook Configuration
Storybook配置
Main Configuration (main.ts
)
main.ts主配置文件(main.ts
)
main.tstypescript
import type { StorybookConfig } from '@storybook/angular';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-interactions',
'@storybook/addon-links',
],
framework: {
name: '@storybook/angular',
options: {},
},
docs: {
autodocs: 'tag', // Generate docs for components with 'autodocs' tag
},
};
export default config;typescript
import type { StorybookConfig } from '@storybook/angular';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-interactions',
'@storybook/addon-links',
],
framework: {
name: '@storybook/angular',
options: {},
},
docs: {
autodocs: 'tag', // 为带有'autodocs'标签的组件生成文档
},
};
export default config;Preview Configuration (preview.ts
)
preview.ts预览配置文件(preview.ts
)
preview.tstypescript
import type { Preview } from '@storybook/angular';
import { setCompodocJson } from '@storybook/addon-docs/angular';
import docJson from '../documentation.json';
setCompodocJson(docJson);
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#F8F9FF' },
{ name: 'dark', value: '#1E293B' },
{ name: 'white', value: '#FFFFFF' },
],
},
viewport: {
viewports: {
mobile: {
name: 'Mobile',
styles: { width: '375px', height: '667px' },
type: 'mobile',
},
tablet: {
name: 'Tablet',
styles: { width: '768px', height: '1024px' },
type: 'tablet',
},
desktop: {
name: 'Desktop',
styles: { width: '1440px', height: '900px' },
type: 'desktop',
},
},
},
},
};
export default preview;typescript
import type { Preview } from '@storybook/angular';
import { setCompodocJson } from '@storybook/addon-docs/angular';
import docJson from '../documentation.json';
setCompodocJson(docJson);
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#F8F9FF' },
{ name: 'dark', value: '#1E293B' },
{ name: 'white', value: '#FFFFFF' },
],
},
viewport: {
viewports: {
mobile: {
name: 'Mobile',
styles: { width: '375px', height: '667px' },
type: 'mobile',
},
tablet: {
name: 'Tablet',
styles: { width: '768px', height: '1024px' },
type: 'tablet',
},
desktop: {
name: 'Desktop',
styles: { width: '1440px', height: '900px' },
type: 'desktop',
},
},
},
},
};
export default preview;Essential Addons
必备插件
@storybook/addon-essentials
@storybook/addon-essentials
Includes: Controls, Actions, Viewport, Backgrounds, Toolbars, Measure, Outline
Usage:
- Controls - Interactive component props
- Actions - Log component events (@Output)
- Viewport - Test responsive behavior
- Backgrounds - Test on different backgrounds
包含:Controls、Actions、Viewport、Backgrounds、Toolbars、Measure、Outline
用法:
- Controls - 交互式组件属性
- Actions - 记录组件事件(@Output)
- Viewport - 测试响应式表现
- Backgrounds - 在不同背景上测试
@storybook/addon-a11y
@storybook/addon-a11y
Accessibility testing with axe-core
Usage:
typescript
export const MyComponent: Story = {
parameters: {
a11y: {
config: {
rules: [
{
id: 'color-contrast',
enabled: true,
},
],
},
},
},
};Check for:
- Color contrast (WCAG AA: 4.5:1 for text, 3:1 for UI)
- ARIA labels
- Keyboard navigation
- Focus indicators
- Semantic HTML
基于axe-core的无障碍测试
用法:
typescript
export const MyComponent: Story = {
parameters: {
a11y: {
config: {
rules: [
{
id: 'color-contrast',
enabled: true,
},
],
},
},
},
};检查项:
- 颜色对比度(WCAG AA:文本4.5:1,UI组件3:1)
- ARIA标签
- 键盘导航
- 焦点指示器
- 语义化HTML
@storybook/addon-interactions
@storybook/addon-interactions
Test user interactions
typescript
import { within, userEvent } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
export const ClickButton: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
await userEvent.click(button);
await expect(button).toHaveClass('clicked');
},
};测试用户交互
typescript
import { within, userEvent } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
export const ClickButton: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
await userEvent.click(button);
await expect(button).toHaveClass('clicked');
},
};Component Development Workflow
组件开发工作流
Step 1: Create Component
步骤1:创建组件
bash
undefinedbash
undefinedGenerate component
生成组件
cd apps/frontend
ng generate component components/button --standalone
undefinedcd apps/frontend
ng generate component components/button --standalone
undefinedStep 2: Create Story
步骤2:创建故事
bash
undefinedbash
undefinedCreate story file
创建故事文件
touch src/app/components/button/button.stories.ts
undefinedtouch src/app/components/button/button.stories.ts
undefinedStep 3: Develop in Storybook
步骤3:在Storybook中开发
bash
undefinedbash
undefinedRun Storybook
启动Storybook
npm run storybook
npm run storybook
Storybook opens at localhost:6006
Storybook将在localhost:6006打开
Hot reload automatically updates as you code
热重载会在你编码时自动更新
undefinedundefinedStep 4: Test All States
步骤4:测试所有状态
Create stories for:
- Default state
- All variants (primary, secondary, danger, etc.)
- All sizes (small, medium, large)
- Disabled state
- Loading state
- Error state
- Empty state (if applicable)
- With long text / edge cases
为以下状态创建故事:
- 默认状态
- 所有变体(primary、secondary、danger等)
- 所有尺寸(small、medium、large)
- 禁用状态
- 加载状态
- 错误状态
- 空状态(如适用)
- 长文本/边缘情况
Step 5: Accessibility Check
步骤5:无障碍检查
- Use a11y addon (bottom panel)
- Check all violations
- Fix color contrast issues
- Add ARIA labels
- Test keyboard navigation
- 使用a11y插件(底部面板)
- 检查所有违规项
- 修复颜色对比度问题
- 添加ARIA标签
- 测试键盘导航
Step 6: Visual Review
步骤6:视觉评审
- Test on all viewports (mobile, tablet, desktop)
- Test on all backgrounds (light, dark, white)
- Check hover states
- Check focus states
- Check animations
- 在所有视口(移动端、平板、桌面)测试
- 在所有背景(浅色、深色、白色)测试
- 检查悬停状态
- 检查焦点状态
- 检查动画
Step 7: Integration
步骤7:集成
Once component is complete in Storybook:
- Import into parent component
- Use in actual app
- E2E test in real context
一旦组件在Storybook中开发完成:
- 导入到父组件
- 在实际应用中使用
- 在真实上下文进行端到端测试
Common Commands
常用命令
Development
开发
bash
undefinedbash
undefinedStart Storybook dev server
启动Storybook开发服务器
npm run storybook
npm run storybook
Build static Storybook
构建静态Storybook
npm run build-storybook
npm run build-storybook
Generate Compodoc documentation
生成Compodoc文档
npm run storybook:docs
npm run storybook:docs
Run both Compodoc and Storybook
同时运行Compodoc和Storybook
npm run storybook:docs && npm run storybook
undefinednpm run storybook:docs && npm run storybook
undefinedStory Creation
故事创建
bash
undefinedbash
undefinedCreate new story file
创建新的故事文件
touch src/app/components/my-component/my-component.stories.ts
touch src/app/components/my-component/my-component.stories.ts
Follow naming convention: {component-name}.stories.ts
遵循命名规范:{component-name}.stories.ts
undefinedundefinedTroubleshooting
故障排除
Issue: Component not rendering
问题:组件未渲染
Solution:
- Check that component is standalone or imported in moduleMetadata
- Check that all dependencies are provided (services, HTTP client)
- Check console for errors
解决方案:
- 检查组件是否为独立组件或已在moduleMetadata中导入
- 检查是否提供了所有依赖项(服务、HTTP客户端)
- 检查控制台错误
Issue: Styles not loading
问题:样式未加载
Solution:
- Import global styles in
preview.ts - Check component styleUrls path
- Ensure CSS variables are defined
解决方案:
- 在中导入全局样式
preview.ts - 检查组件styleUrls路径
- 确保CSS变量已定义
Issue: Compodoc failing
问题:Compodoc执行失败
Solution:
bash
undefined解决方案:
bash
undefinedRegenerate documentation
重新生成文档
npm run storybook:docs
npm run storybook:docs
Check for TypeScript errors
检查TypeScript错误
npm run type-check
undefinednpm run type-check
undefinedIssue: Hot reload not working
问题:热重载不工作
Solution:
- Restart Storybook
- Clear browser cache
- Check for conflicting ports
解决方案:
- 重启Storybook
- 清除浏览器缓存
- 检查端口冲突
Best Practices Checklist
最佳实践清单
Every Component Story Should Have:
每个组件故事应包含:
- Multiple story variants (Default, Disabled, Loading, etc.)
- All input combinations tested
- Accessibility testing enabled (a11y addon)
- Proper argTypes with descriptions
- Tags: ['autodocs'] for automatic documentation
- JSDoc comments on @Input() and @Output()
- 多个故事变体(Default、Disabled、Loading等)
- 所有输入组合已测试
- 启用无障碍测试(a11y插件)
- 带描述的正确argTypes
- Tags: ['autodocs']用于自动生成文档
- @Input()和@Output()上的JSDoc注释
Every Design System Story Should Have:
每个设计系统故事应包含:
- Visual representation
- Code examples
- Usage guidelines
- Do's and Don'ts (optional)
- 视觉展示
- 代码示例
- 使用指南
- 注意事项(可选)
Story Organization:
故事组织:
- Hierarchical naming (Components/, Pages/, Design System/)
- Alphabetical ordering within categories
- Consistent naming convention
- 层级命名(Components/、Pages/、Design System/)
- 类别内按字母顺序排序
- 一致的命名规范
Integration with CI/CD
与CI/CD集成
Build Storybook in CI
在CI中构建Storybook
yaml
undefinedyaml
undefinedGitHub Actions example
GitHub Actions示例
- name: Build Storybook run: | cd apps/frontend npm run storybook:docs npm run build-storybook
undefined- name: Build Storybook run: | cd apps/frontend npm run storybook:docs npm run build-storybook
undefinedDeploy Storybook
部署Storybook
bash
undefinedbash
undefinedBuild static version
构建静态版本
npm run build-storybook
npm run build-storybook
Deploy to static hosting (Netlify, Vercel, GitHub Pages)
部署到静态托管服务(Netlify、Vercel、GitHub Pages)
Output directory: storybook-static/
输出目录:storybook-static/
undefinedundefinedVisual Regression Testing
视觉回归测试
Consider adding:
- Chromatic (visual testing platform)
- Percy (visual review)
- Storybook test runner
考虑添加:
- Chromatic(视觉测试平台)
- Percy(视觉评审)
- Storybook测试运行器
Success Criteria
成功标准
After implementing this skill:
- ✅ All components have corresponding stories
- ✅ Design system fully documented
- ✅ Accessibility testing on every component
- ✅ Visual regression testing enabled
- ✅ Developers can create stories in < 5 minutes
- ✅ Storybook deployed for team collaboration
实施此技能后:
- ✅ 所有组件都有对应的故事
- ✅ 设计系统已完整文档化
- ✅ 每个组件都进行无障碍测试
- ✅ 已启用视觉回归测试
- ✅ 开发者可以在5分钟内创建故事
- ✅ Storybook已部署供团队协作
References
参考资料
Official Documentation
官方文档
Angular-Specific
Angular专属
Related Skills
相关技能
Use together with:
- frontend skill - Angular 21+ component development
- frontend-design skill - UI design principles
- ux-design skill - User experience patterns
配合以下技能使用:
- frontend技能 - Angular 21+组件开发
- frontend-design技能 - UI设计原则
- ux-design技能 - 用户体验模式