Files
2026-07-13 21:36:22 +08:00

4.2 KiB
Raw Permalink Blame History

组件组合

目录

  • 条目始终位于其 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

DialogTitleSheetTitleDrawerTitle 是无障碍所必需的。如果希望视觉上隐藏,请使用 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">