storybook-args-controls

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Storybook - Args and Controls

Storybook - Args 与 Controls

Configure interactive controls and args to make stories dynamic and explorable, allowing designers and developers to test component variations in real-time.
配置交互式控件和Args,让故事变得动态且可探索,使设计师和开发人员能够实时测试组件的不同变体。

Key Concepts

核心概念

Args

Args

Args are inputs to components that Storybook tracks and makes interactive:
typescript
export const Primary: Story = {
  args: {
    label: 'Button',
    primary: true,
    size: 'medium',
    onClick: () => alert('clicked'),
  },
};
Args是Storybook跟踪并使其可交互的组件输入:
typescript
export const Primary: Story = {
  args: {
    label: 'Button',
    primary: true,
    size: 'medium',
    onClick: () => alert('clicked'),
  },
};

ArgTypes

ArgTypes

ArgTypes define metadata about args, including control types and documentation:
typescript
const meta = {
  component: Button,
  argTypes: {
    backgroundColor: {
      control: 'color',
      description: 'Background color of the button',
    },
    size: {
      control: { type: 'select' },
      options: ['small', 'medium', 'large'],
      description: 'Size variant',
    },
    onClick: {
      action: 'clicked',
    },
  },
} satisfies Meta<typeof Button>;
ArgTypes定义Args的元数据,包括控件类型和文档说明:
typescript
const meta = {
  component: Button,
  argTypes: {
    backgroundColor: {
      control: 'color',
      description: 'Background color of the button',
    },
    size: {
      control: { type: 'select' },
      options: ['small', 'medium', 'large'],
      description: 'Size variant',
    },
    onClick: {
      action: 'clicked',
    },
  },
} satisfies Meta<typeof Button>;

Control Types

控件类型

Storybook provides various control types for different data types:
  • text
    - String input
  • number
    - Number input with validation
  • boolean
    - Checkbox toggle
  • color
    - Color picker
  • date
    - Date picker
  • select
    - Dropdown menu
  • radio
    - Radio buttons
  • range
    - Slider with min/max
  • object
    - JSON editor
  • array
    - Array editor
Storybook为不同的数据类型提供了多种控件类型:
  • text
    - 字符串输入框
  • number
    - 带验证的数字输入框
  • boolean
    - 复选框开关
  • color
    - 颜色选择器
  • date
    - 日期选择器
  • select
    - 下拉菜单
  • radio
    - 单选按钮
  • range
    - 带最小值/最大值的滑块
  • object
    - JSON编辑器
  • array
    - 数组编辑器

Best Practices

最佳实践

1. Infer Controls from TypeScript

1. 从TypeScript推断控件

Let Storybook auto-generate controls from TypeScript types:
typescript
interface ButtonProps {
  label: string;
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  backgroundColor?: string;
  onClick?: () => void;
}

export const Button: React.FC<ButtonProps> = ({ ... }) => { ... };

const meta = {
  component: Button,
  // Controls inferred from ButtonProps
} satisfies Meta<typeof Button>;
让Storybook从TypeScript类型自动生成控件:
typescript
interface ButtonProps {
  label: string;
  primary?: boolean;
  size?: 'small' | 'medium' | 'large';
  backgroundColor?: string;
  onClick?: () => void;
}

export const Button: React.FC<ButtonProps> = ({ ... }) => { ... };

const meta = {
  component: Button,
  // Controls inferred from ButtonProps
} satisfies Meta<typeof Button>;

2. Customize Control Types When Needed

2. 必要时自定义控件类型

Override auto-inferred controls for better UX:
typescript
const meta = {
  component: ColorPicker,
  argTypes: {
    color: {
      control: 'color',  // Override default text input
    },
    opacity: {
      control: { type: 'range', min: 0, max: 1, step: 0.1 },
    },
    preset: {
      control: 'select',
      options: ['primary', 'secondary', 'success', 'warning', 'danger'],
    },
  },
} satisfies Meta<typeof ColorPicker>;
覆盖自动推断的控件以获得更好的用户体验:
typescript
const meta = {
  component: ColorPicker,
  argTypes: {
    color: {
      control: 'color',  // Override default text input
    },
    opacity: {
      control: { type: 'range', min: 0, max: 1, step: 0.1 },
    },
    preset: {
      control: 'select',
      options: ['primary', 'secondary', 'success', 'warning', 'danger'],
    },
  },
} satisfies Meta<typeof ColorPicker>;

3. Use Actions for Event Handlers

3. 为事件处理程序使用Actions

Track event callbacks in the Actions panel:
typescript
const meta = {
  component: Form,
  argTypes: {
    onSubmit: { action: 'submitted' },
    onChange: { action: 'changed' },
    onError: { action: 'error occurred' },
  },
} satisfies Meta<typeof Form>;

export const Default: Story = {
  args: {
    onSubmit: (data) => console.log('Form data:', data),
  },
};
在Actions面板中跟踪事件回调:
typescript
const meta = {
  component: Form,
  argTypes: {
    onSubmit: { action: 'submitted' },
    onChange: { action: 'changed' },
    onError: { action: 'error occurred' },
  },
} satisfies Meta<typeof Form>;

export const Default: Story = {
  args: {
    onSubmit: (data) => console.log('Form data:', data),
  },
};

4. Set Sensible Defaults

4. 设置合理的默认值

Provide default args at the meta level:
typescript
const meta = {
  component: Slider,
  args: {
    min: 0,
    max: 100,
    step: 1,
    value: 50,
  },
  argTypes: {
    value: {
      control: { type: 'range', min: 0, max: 100, step: 1 },
    },
  },
} satisfies Meta<typeof Slider>;
在元数据级别提供默认Args:
typescript
const meta = {
  component: Slider,
  args: {
    min: 0,
    max: 100,
    step: 1,
    value: 50,
  },
  argTypes: {
    value: {
      control: { type: 'range', min: 0, max: 100, step: 1 },
    },
  },
} satisfies Meta<typeof Slider>;

5. Document Args

5. 为Args添加文档说明

Add descriptions to help users understand each arg:
typescript
const meta = {
  component: Tooltip,
  argTypes: {
    placement: {
      control: 'select',
      options: ['top', 'right', 'bottom', 'left'],
      description: 'Position of the tooltip relative to its trigger',
      table: {
        defaultValue: { summary: 'top' },
        type: { summary: 'string' },
      },
    },
    delay: {
      control: { type: 'number', min: 0, max: 2000, step: 100 },
      description: 'Delay in milliseconds before showing the tooltip',
    },
  },
} satisfies Meta<typeof Tooltip>;
添加描述以帮助用户理解每个Args:
typescript
const meta = {
  component: Tooltip,
  argTypes: {
    placement: {
      control: 'select',
      options: ['top', 'right', 'bottom', 'left'],
      description: 'Position of the tooltip relative to its trigger',
      table: {
        defaultValue: { summary: 'top' },
        type: { summary: 'string' },
      },
    },
    delay: {
      control: { type: 'number', min: 0, max: 2000, step: 100 },
      description: 'Delay in milliseconds before showing the tooltip',
    },
  },
} satisfies Meta<typeof Tooltip>;

Common Patterns

常见模式

Enum/Union Type Controls

枚举/联合类型控件

typescript
type ButtonVariant = 'primary' | 'secondary' | 'danger';

const meta = {
  component: Button,
  argTypes: {
    variant: {
      control: 'radio',
      options: ['primary', 'secondary', 'danger'] satisfies ButtonVariant[],
    },
  },
} satisfies Meta<typeof Button>;
typescript
type ButtonVariant = 'primary' | 'secondary' | 'danger';

const meta = {
  component: Button,
  argTypes: {
    variant: {
      control: 'radio',
      options: ['primary', 'secondary', 'danger'] satisfies ButtonVariant[],
    },
  },
} satisfies Meta<typeof Button>;

Complex Object Controls

复杂对象控件

typescript
const meta = {
  component: Chart,
  argTypes: {
    data: {
      control: 'object',
      description: 'Chart data points',
    },
    options: {
      control: 'object',
      description: 'Chart configuration',
    },
  },
} satisfies Meta<typeof Chart>;

export const Default: Story = {
  args: {
    data: [
      { x: 0, y: 10 },
      { x: 1, y: 20 },
      { x: 2, y: 15 },
    ],
    options: {
      showLegend: true,
      animate: true,
    },
  },
};
typescript
const meta = {
  component: Chart,
  argTypes: {
    data: {
      control: 'object',
      description: 'Chart data points',
    },
    options: {
      control: 'object',
      description: 'Chart configuration',
    },
  },
} satisfies Meta<typeof Chart>;

export const Default: Story = {
  args: {
    data: [
      { x: 0, y: 10 },
      { x: 1, y: 20 },
      { x: 2, y: 15 },
    ],
    options: {
      showLegend: true,
      animate: true,
    },
  },
};

Conditional Controls

条件控件

Hide irrelevant controls based on other arg values:
typescript
const meta = {
  component: Input,
  argTypes: {
    type: {
      control: 'select',
      options: ['text', 'number', 'email', 'password'],
    },
    min: {
      control: 'number',
      if: { arg: 'type', eq: 'number' },  // Only show for number inputs
    },
    max: {
      control: 'number',
      if: { arg: 'type', eq: 'number' },
    },
    showPasswordToggle: {
      control: 'boolean',
      if: { arg: 'type', eq: 'password' },
    },
  },
} satisfies Meta<typeof Input>;
根据其他Arg值隐藏不相关的控件:
typescript
const meta = {
  component: Input,
  argTypes: {
    type: {
      control: 'select',
      options: ['text', 'number', 'email', 'password'],
    },
    min: {
      control: 'number',
      if: { arg: 'type', eq: 'number' },  // Only show for number inputs
    },
    max: {
      control: 'number',
      if: { arg: 'type', eq: 'number' },
    },
    showPasswordToggle: {
      control: 'boolean',
      if: { arg: 'type', eq: 'password' },
    },
  },
} satisfies Meta<typeof Input>;

Disable Controls

禁用控件

Disable controls for props that shouldn't be editable:
typescript
const meta = {
  component: DataTable,
  argTypes: {
    data: {
      control: false,  // Disable control (use args instead)
    },
    onSort: {
      table: { disable: true },  // Hide from docs table
    },
  },
} satisfies Meta<typeof DataTable>;
禁用不应被编辑的属性对应的控件:
typescript
const meta = {
  component: DataTable,
  argTypes: {
    data: {
      control: false,  // Disable control (use args instead)
    },
    onSort: {
      table: { disable: true },  // Hide from docs table
    },
  },
} satisfies Meta<typeof DataTable>;

Grouping Controls

控件分组

Organize controls into logical categories:
typescript
const meta = {
  component: Modal,
  argTypes: {
    // Appearance
    title: {
      control: 'text',
      table: { category: 'Appearance' },
    },
    size: {
      control: 'select',
      options: ['small', 'medium', 'large'],
      table: { category: 'Appearance' },
    },

    // Behavior
    closeOnEscape: {
      control: 'boolean',
      table: { category: 'Behavior' },
    },
    closeOnOverlayClick: {
      control: 'boolean',
      table: { category: 'Behavior' },
    },

    // Events
    onClose: {
      action: 'closed',
      table: { category: 'Events' },
    },
  },
} satisfies Meta<typeof Modal>;
将控件组织为逻辑分类:
typescript
const meta = {
  component: Modal,
  argTypes: {
    // Appearance
    title: {
      control: 'text',
      table: { category: 'Appearance' },
    },
    size: {
      control: 'select',
      options: ['small', 'medium', 'large'],
      table: { category: 'Appearance' },
    },

    // Behavior
    closeOnEscape: {
      control: 'boolean',
      table: { category: 'Behavior' },
    },
    closeOnOverlayClick: {
      control: 'boolean',
      table: { category: 'Behavior' },
    },

    // Events
    onClose: {
      action: 'closed',
      table: { category: 'Events' },
    },
  },
} satisfies Meta<typeof Modal>;

Advanced Patterns

高级模式

Custom Control Components

自定义控件组件

Create custom controls for specialized inputs:
typescript
import { useArgs } from '@storybook/preview-api';

const meta = {
  component: GradientPicker,
  argTypes: {
    gradient: {
      control: {
        type: 'object',
      },
    },
  },
} satisfies Meta<typeof GradientPicker>;

export const Custom: Story = {
  render: (args) => {
    const [{ gradient }, updateArgs] = useArgs();
    return (
      <GradientPicker
        {...args}
        gradient={gradient}
        onChange={(newGradient) => updateArgs({ gradient: newGradient })}
      />
    );
  },
};
为特殊输入创建自定义控件:
typescript
import { useArgs } from '@storybook/preview-api';

const meta = {
  component: GradientPicker,
  argTypes: {
    gradient: {
      control: {
        type: 'object',
      },
    },
  },
} satisfies Meta<typeof GradientPicker>;

export const Custom: Story = {
  render: (args) => {
    const [{ gradient }, updateArgs] = useArgs();
    return (
      <GradientPicker
        {...args}
        gradient={gradient}
        onChange={(newGradient) => updateArgs({ gradient: newGradient })}
      />
    );
  },
};

Dynamic ArgTypes

动态ArgTypes

Generate argTypes programmatically:
typescript
const themes = ['light', 'dark', 'system'] as const;

const meta = {
  component: ThemeProvider,
  argTypes: {
    theme: {
      control: 'select',
      options: themes,
      mapping: Object.fromEntries(
        themes.map(theme => [theme, theme])
      ),
    },
  },
} satisfies Meta<typeof ThemeProvider>;
以编程方式生成ArgTypes:
typescript
const themes = ['light', 'dark', 'system'] as const;

const meta = {
  component: ThemeProvider,
  argTypes: {
    theme: {
      control: 'select',
      options: themes,
      mapping: Object.fromEntries(
        themes.map(theme => [theme, theme])
      ),
    },
  },
} satisfies Meta<typeof ThemeProvider>;

Anti-Patterns

反模式

❌ Don't Override Args in Render

❌ 不要在Render中覆盖Args

typescript
// Bad
export const Example: Story = {
  render: (args) => <Button {...args} label="Hardcoded" />,
};
typescript
// Good
export const Example: Story = {
  args: {
    label: 'Hardcoded',
  },
};
typescript
// Bad
export const Example: Story = {
  render: (args) => <Button {...args} label="Hardcoded" />,
};
typescript
// Good
export const Example: Story = {
  args: {
    label: 'Hardcoded',
  },
};

❌ Don't Use Controls for Static Data

❌ 不要为静态数据使用控件

typescript
// Bad - Large mock data in controls
export const WithData: Story = {
  args: {
    items: Array.from({ length: 1000 }, (_, i) => ({ id: i, ... })),
  },
  argTypes: {
    items: { control: 'object' },  // Don't make editable
  },
};
typescript
// Good - Disable control for mock data
export const WithData: Story = {
  args: {
    items: mockLargeDataset,
  },
  argTypes: {
    items: { control: false },
  },
};
typescript
// Bad - Large mock data in controls
export const WithData: Story = {
  args: {
    items: Array.from({ length: 1000 }, (_, i) => ({ id: i, ... })),
  },
  argTypes: {
    items: { control: 'object' },  // Don't make editable
  },
};
typescript
// Good - Disable control for mock data
export const WithData: Story = {
  args: {
    items: mockLargeDataset,
  },
  argTypes: {
    items: { control: false },
  },
};

❌ Don't Duplicate ArgTypes Across Stories

❌ 不要在多个故事中重复ArgTypes

typescript
// Bad
export const Story1: Story = {
  argTypes: {
    size: { control: 'select', options: ['small', 'large'] },
  },
};
export const Story2: Story = {
  argTypes: {
    size: { control: 'select', options: ['small', 'large'] },
  },
};
typescript
// Good - Define at meta level
const meta = {
  component: Button,
  argTypes: {
    size: { control: 'select', options: ['small', 'large'] },
  },
} satisfies Meta<typeof Button>;
typescript
// Bad
export const Story1: Story = {
  argTypes: {
    size: { control: 'select', options: ['small', 'large'] },
  },
};
export const Story2: Story = {
  argTypes: {
    size: { control: 'select', options: ['small', 'large'] },
  },
};
typescript
// Good - Define at meta level
const meta = {
  component: Button,
  argTypes: {
    size: { control: 'select', options: ['small', 'large'] },
  },
} satisfies Meta<typeof Button>;

Related Skills

相关技能

  • storybook-story-writing: Writing well-structured stories
  • storybook-component-documentation: Auto-generating docs from controls
  • storybook-play-functions: Testing interactions with args
  • storybook-story-writing: 编写结构清晰的故事
  • storybook-component-documentation: 从控件自动生成文档
  • storybook-play-functions: 使用Args测试交互