196 lines
4.2 KiB
Markdown
196 lines
4.2 KiB
Markdown
# 组件组合
|
||
|
||
## 目录
|
||
|
||
- 条目始终位于其 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
|
||
<SelectContent>
|
||
<SelectItem value="apple">Apple</SelectItem>
|
||
<SelectItem value="banana">Banana</SelectItem>
|
||
</SelectContent>
|
||
```
|
||
|
||
**正确写法:**
|
||
|
||
```tsx
|
||
<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
|
||
|
||
```tsx
|
||
<Alert>
|
||
<AlertTitle>警告</AlertTitle>
|
||
<AlertDescription>某些内容需要关注。</AlertDescription>
|
||
</Alert>
|
||
```
|
||
|
||
---
|
||
|
||
## 空状态使用 Empty 组件
|
||
|
||
```tsx
|
||
<Empty>
|
||
<EmptyHeader>
|
||
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
|
||
<EmptyTitle>暂无项目</EmptyTitle>
|
||
<EmptyDescription>立即创建一个新项目开始使用。</EmptyDescription>
|
||
</EmptyHeader>
|
||
<EmptyContent>
|
||
<Button>创建项目</Button>
|
||
</EmptyContent>
|
||
</Empty>
|
||
```
|
||
|
||
---
|
||
|
||
## 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
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>编辑个人资料</DialogTitle>
|
||
<DialogDescription>更新您的个人资料。</DialogDescription>
|
||
</DialogHeader>
|
||
...
|
||
</DialogContent>
|
||
```
|
||
|
||
---
|
||
|
||
## Card 结构
|
||
|
||
使用完整的组合方式——不要把所有内容都塞进 `CardContent`:
|
||
|
||
```tsx
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>团队成员</CardTitle>
|
||
<CardDescription>管理您的团队。</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>...</CardContent>
|
||
<CardFooter>
|
||
<Button>邀请</Button>
|
||
</CardFooter>
|
||
</Card>
|
||
```
|
||
|
||
---
|
||
|
||
## Button 没有 isPending 或 isLoading 属性
|
||
|
||
配合 `Spinner` + `data-icon` + `disabled` 进行组合:
|
||
|
||
```tsx
|
||
<Button disabled>
|
||
<Spinner data-icon="inline-start" />
|
||
保存中...
|
||
</Button>
|
||
```
|
||
|
||
---
|
||
|
||
## TabsTrigger 必须位于 TabsList 内部
|
||
|
||
切勿在 `Tabs` 内部直接渲染 `TabsTrigger`——始终包裹在 `TabsList` 中:
|
||
|
||
```tsx
|
||
<Tabs defaultValue="account">
|
||
<TabsList>
|
||
<TabsTrigger value="account">账户</TabsTrigger>
|
||
<TabsTrigger value="password">密码</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="account">...</TabsContent>
|
||
</Tabs>
|
||
```
|
||
|
||
---
|
||
|
||
## Avatar 始终需要 AvatarFallback
|
||
|
||
始终包含 `AvatarFallback`,用于图片加载失败时的降级显示:
|
||
|
||
```tsx
|
||
<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">` |
|