Files
skillhub-098-shadcn/rules/composition.md
T
2026-07-13 21:36:22 +08:00

196 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 组件组合
## 目录
- 条目始终位于其 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">` |