如何在Storybook中使用Radix UI复合组件并控制其Props?
解决Radix UI封装Checkbox在Storybook中无法控制原生Props的问题
核心原因
封装组件时未透传Radix原生Root和Indicator的Props,或Storybook未正确识别组件的类型定义。
具体解决方案
1. 确保封装组件透传所有原生Props
封装时必须将Radix CheckboxRoot的所有原生Props通过...props透传,同时如果需要控制Indicator的Props,也要单独透传其属性。
示例代码:
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; import { cn } from '@/lib/utils'; // 自定义样式拼接工具 // 继承Radix原生CheckboxRoot的Props,同时添加自定义属性 type CheckboxProps = CheckboxPrimitive.CheckboxProps & { indicatorClassName?: string; // 可选:直接透传Indicator的所有Props indicatorProps?: React.ComponentProps<typeof CheckboxPrimitive.Indicator>; }; const Checkbox = ({ className, indicatorClassName, indicatorProps, ...props }: CheckboxProps) => { return ( <CheckboxPrimitive.Root className={cn('flex h-4 w-4 items-center justify-center rounded border border-slate-300', className)} {...props} // 透传Root的所有原生Props > <CheckboxPrimitive.Indicator className={cn('text-white', indicatorClassName)} {...indicatorProps} // 透传Indicator的Props > <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="h-3 w-3"> <path fillRule="evenodd" d="M19.916 4.626a.75.75 0 0 1 .208 1.04l-9 13.5a.75.75 0 0 1-1.154.114l-6-6a.75.75 0 0 1 1.06-1.06l5.353 5.353 8.493-12.739a.75.75 0 0 1 1.04-.208Z" clipRule="evenodd" /> </svg> </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> ); }; export default Checkbox;
2. 配置Storybook识别组件类型
确保Storybook能正确解析组件的TypeScript类型,自动生成Controls面板:
- 安装必要插件:确保已安装
@storybook/addon-controls和@storybook/addon-docs,并在.storybook/main.ts中注册:
import type { StorybookConfig } from '@storybook/react-vite'; const config: StorybookConfig = { addons: [ '@storybook/addon-controls', '@storybook/addon-docs' ], // 其他配置项 }; export default config;
- 在stories文件中显式关联组件类型:
import Checkbox from './Checkbox'; import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta<typeof Checkbox> = { component: Checkbox, title: 'Components/Checkbox', // 可选:自动推断不全时,手动补充argTypes argTypes: { checked: { control: 'boolean', description: '控制选中状态' }, disabled: { control: 'boolean', description: '禁用复选框' }, defaultChecked: { control: 'boolean', description: '默认选中状态' }, name: { control: 'text', description: '表单name属性' }, // 若透传了indicatorProps,可手动添加对应控制项 indicatorProps: { control: 'object', description: 'Indicator组件的原生属性' } } }; export default meta; type Story = StoryObj<typeof Checkbox>; export const Default: Story = { args: { checked: false, disabled: false } };
3. 可选:将IndicatorProps展开到组件顶层
如果希望在Storybook面板中直接控制Indicator的单个属性(而非嵌套的indicatorProps对象),可以拆分Root和Indicator的Props:
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; import { cn } from '@/lib/utils'; // 合并Root和Indicator的Props,排除重复的children属性 type CheckboxProps = CheckboxPrimitive.CheckboxProps & Omit<React.ComponentProps<typeof CheckboxPrimitive.Indicator>, 'children'> & { className?: string; indicatorClassName?: string; }; const Checkbox = ({ className, indicatorClassName, ...props }: CheckboxProps) => { // 分离Root和Indicator的Props const rootPropKeys = Object.keys(CheckboxPrimitive.CheckboxProps); const rootProps = Object.fromEntries( Object.entries(props).filter(([key]) => rootPropKeys.includes(key)) ) as CheckboxPrimitive.CheckboxProps; const indicatorProps = Object.fromEntries( Object.entries(props).filter(([key]) => !rootPropKeys.includes(key)) ) as Omit<React.ComponentProps<typeof CheckboxPrimitive.Indicator>, 'children'>; return ( <CheckboxPrimitive.Root className={cn('flex h-4 w-4 items-center justify-center rounded border border-slate-300', className)} {...rootProps} > <CheckboxPrimitive.Indicator className={cn('text-white', indicatorClassName)} {...indicatorProps} > {/* 指示器内容 */} </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> ); };
这样Storybook会自动将Indicator的原生Props(如aria-label、style等)显示在Controls面板的顶层,方便直接控制。
内容的提问来源于stack exchange,提问作者darkhorse
相关产品推荐
相关产品推荐

