agent-storybook

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Storybook 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.ts
apps/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.ts

Story Naming Convention

故事命名规范

Hierarchical Structure

层级结构

Use
/
separator for organization:
Components:
  • Components/Button
    - Basic button
  • Components/Forms/Input
    - Nested form input
  • Components/Cards/TaskCard
    - Card components
Pages:
  • Pages/Home
    - Page-level story
  • Pages/Auth/Login
    - Nested page
Design System:
  • Design System/Colors
    - Color palette
  • Design System/Typography
    - Font styles
  • Design System/Spacing
    - Spacing tokens
Examples:
  • Examples/Forms/LoginForm
    - Complete form example
  • Examples/Layouts/Dashboard
    - Layout example
使用
/
分隔符进行组织:
组件:
  • 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
    open
    control
  • 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
.claude/skills/modal-components/SKILL.md
for Modal API usage.
重要提示:所有模态框组件必须配备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键
查看
.claude/skills/modal-components/SKILL.md
获取模态框API用法。

Design 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

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', // 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

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;
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
undefined
bash
undefined

Generate component

生成组件

cd apps/frontend ng generate component components/button --standalone
undefined
cd apps/frontend ng generate component components/button --standalone
undefined

Step 2: Create Story

步骤2:创建故事

bash
undefined
bash
undefined

Create story file

创建故事文件

touch src/app/components/button/button.stories.ts
undefined
touch src/app/components/button/button.stories.ts
undefined

Step 3: Develop in Storybook

步骤3:在Storybook中开发

bash
undefined
bash
undefined

Run Storybook

启动Storybook

npm run storybook
npm run storybook

Storybook opens at localhost:6006

Storybook将在localhost:6006打开

Hot reload automatically updates as you code

热重载会在你编码时自动更新

undefined
undefined

Step 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
undefined
bash
undefined

Start 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
undefined
npm run storybook:docs && npm run storybook
undefined

Story Creation

故事创建

bash
undefined
bash
undefined

Create 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

undefined
undefined

Troubleshooting

故障排除

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
undefined

Regenerate documentation

重新生成文档

npm run storybook:docs
npm run storybook:docs

Check for TypeScript errors

检查TypeScript错误

npm run type-check
undefined
npm run type-check
undefined

Issue: 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
undefined
yaml
undefined

GitHub 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
undefined

Deploy Storybook

部署Storybook

bash
undefined
bash
undefined

Build 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/

undefined
undefined

Visual 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技能 - 用户体验模式