4.2 KiB
4.2 KiB
组件组合
目录
- 条目始终位于其 Group 组件内
- 标注提示使用 Alert
- 空状态使用 Empty 组件
- Toast 通知使用 sonner
- 如何选择覆盖层组件
- Dialog、Sheet 和 Drawer 始终需要 Title
- Card 结构
- Button 没有 isPending 或 isLoading 属性
- TabsTrigger 必须位于 TabsList 内部
- Avatar 始终需要 AvatarFallback
- 使用 Separator 替代原生 hr 或 border div
- 使用 Skeleton 作为加载占位符
- 使用 Badge 替代自定义样式 span
条目始终位于其 Group 组件内
切勿直接在内容容器内渲染条目。
错误写法:
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectContent>
正确写法:
<SelectContent>
<SelectGroup>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
</SelectContent>
这适用于所有基于 Group 的组件:
| 条目 | 分组容器 |
|---|---|
SelectItem, SelectLabel |
SelectGroup |
DropdownMenuItem, DropdownMenuLabel, DropdownMenuSub |
DropdownMenuGroup |
MenubarItem |
MenubarGroup |
ContextMenuItem |
ContextMenuGroup |
CommandItem |
CommandGroup |
标注提示使用 Alert
<Alert>
<AlertTitle>警告</AlertTitle>
<AlertDescription>某些内容需要关注。</AlertDescription>
</Alert>
空状态使用 Empty 组件
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
<EmptyTitle>暂无项目</EmptyTitle>
<EmptyDescription>立即创建一个新项目开始使用。</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>创建项目</Button>
</EmptyContent>
</Empty>
Toast 通知使用 sonner
import { toast } from "sonner"
toast.success("更改已保存。")
toast.error("发生错误。")
toast("文件已删除。", {
action: { label: "撤销", onClick: () => undoDelete() },
})
如何选择覆盖层组件
| 使用场景 | 组件 |
|---|---|
| 需要用户输入的专注型任务 | Dialog |
| 破坏性操作确认 | AlertDialog |
| 显示详情或筛选条件的侧面板 | Sheet |
| 移动端优先的底部面板 | Drawer |
| 悬停时快速查看信息 | HoverCard |
| 点击后显示小型上下文内容 | Popover |
Dialog、Sheet 和 Drawer 始终需要 Title
DialogTitle、SheetTitle、DrawerTitle 是无障碍所必需的。如果希望视觉上隐藏,请使用 className="sr-only"。
<DialogContent>
<DialogHeader>
<DialogTitle>编辑个人资料</DialogTitle>
<DialogDescription>更新您的个人资料。</DialogDescription>
</DialogHeader>
...
</DialogContent>
Card 结构
使用完整的组合方式——不要把所有内容都塞进 CardContent:
<Card>
<CardHeader>
<CardTitle>团队成员</CardTitle>
<CardDescription>管理您的团队。</CardDescription>
</CardHeader>
<CardContent>...</CardContent>
<CardFooter>
<Button>邀请</Button>
</CardFooter>
</Card>
Button 没有 isPending 或 isLoading 属性
配合 Spinner + data-icon + disabled 进行组合:
<Button disabled>
<Spinner data-icon="inline-start" />
保存中...
</Button>
TabsTrigger 必须位于 TabsList 内部
切勿在 Tabs 内部直接渲染 TabsTrigger——始终包裹在 TabsList 中:
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">账户</TabsTrigger>
<TabsTrigger value="password">密码</TabsTrigger>
</TabsList>
<TabsContent value="account">...</TabsContent>
</Tabs>
Avatar 始终需要 AvatarFallback
始终包含 AvatarFallback,用于图片加载失败时的降级显示:
<Avatar>
<AvatarImage src="/avatar.png" alt="用户" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
使用现有组件替代自定义标记
| 不要使用 | 请使用 |
|---|---|
<hr> 或 <div className="border-t"> |
<Separator /> |
<div className="animate-pulse"> 配合样式 div |
<Skeleton className="h-4 w-3/4" /> |
<span className="rounded-full bg-green-100 ..."> |
<Badge variant="secondary"> |