项目文件夹

文件
2026-07-13 21:36:22 +08:00

196 行
4.2 KiB
Markdown

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 组件组合
## 目录
- 条目始终位于其 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">` |