# 组件组合
## 目录
- 条目始终位于其 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 组件内
切勿直接在内容容器内渲染条目。
**错误写法:**
```tsx
Apple
Banana
```
**正确写法:**
```tsx
Apple
Banana
```
这适用于所有基于 Group 的组件:
| 条目 | 分组容器 |
|------|---------|
| `SelectItem`, `SelectLabel` | `SelectGroup` |
| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
| `MenubarItem` | `MenubarGroup` |
| `ContextMenuItem` | `ContextMenuGroup` |
| `CommandItem` | `CommandGroup` |
---
## 标注提示使用 Alert
```tsx
警告
某些内容需要关注。
```
---
## 空状态使用 Empty 组件
```tsx
暂无项目
立即创建一个新项目开始使用。
```
---
## Toast 通知使用 sonner
```tsx
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"`。
```tsx
编辑个人资料
更新您的个人资料。
...
```
---
## Card 结构
使用完整的组合方式——不要把所有内容都塞进 `CardContent`:
```tsx
团队成员
管理您的团队。
...
```
---
## Button 没有 isPending 或 isLoading 属性
配合 `Spinner` + `data-icon` + `disabled` 进行组合:
```tsx
```
---
## TabsTrigger 必须位于 TabsList 内部
切勿在 `Tabs` 内部直接渲染 `TabsTrigger`——始终包裹在 `TabsList` 中:
```tsx
账户
密码
...
```
---
## Avatar 始终需要 AvatarFallback
始终包含 `AvatarFallback`,用于图片加载失败时的降级显示:
```tsx
JD
```
---
## 使用现有组件替代自定义标记
| 不要使用 | 请使用 |
|---|---|
| `
` 或 `` | `
` |
| `
` 配合样式 div | `` |
| `` | `` |