Chapter 07

覆盖层组件

Dialog/AlertDialog/Popover/Tooltip/DropdownMenu/ContextMenu——理解 Portal、焦点陷阱与 ARIA,做出真正可访问的浮层。

7.1 关键概念

Portal(传送门)
React 把浮层 DOM 渲染到 <body> 末尾而非组件原位,避开父级 overflow:hiddenz-index 堆叠陷阱。Radix 浮层默认走 Portal。
Focus Trap(焦点陷阱)
模态打开时把 Tab 焦点限制在浮层内,关闭后归还给触发元素。Radix Dialog 自动处理,无障碍必需。
模态 vs 非模态
模态(Dialog/AlertDialog)会加遮罩、锁滚动、拦截外部交互;非模态(Popover/Tooltip)不锁背景,点外部即关。
Dialog
通用模态弹窗,可关闭(Esc/点遮罩/关闭按钮),用于表单、详情、确认等。
AlertDialog
强确认对话框,不能点遮罩关闭,必须选"取消"或"确认",用于删除等破坏性操作。
Popover / Tooltip
Popover 是可交互的悬浮面板(可放表单);Tooltip 是只读悬浮提示,靠 hover/focus 触发,需 TooltipProvider 包裹。
DropdownMenu / ContextMenu
DropdownMenu 由点击触发(按钮菜单);ContextMenu 由右键触发(上下文菜单)。两者支持子菜单、快捷键、勾选项、单选组。

7.2 安装

$ npx shadcn@latest add dialog alert-dialog popover \
    tooltip dropdown-menu context-menu

7.3 Dialog:模态弹窗

import {
  Dialog, DialogContent, DialogDescription, DialogFooter,
  DialogHeader, DialogTitle, DialogTrigger, DialogClose,
} from "@/components/ui/dialog";

<Dialog>
  <DialogTrigger asChild><Button>编辑资料</Button></DialogTrigger>
  <DialogContent className="sm:max-w-[425px]">
    <DialogHeader>
      <DialogTitle>编辑资料</DialogTitle>
      <DialogDescription>修改后点保存。</DialogDescription>
    </DialogHeader>
    <div className="grid gap-4 py-4">
      <Input placeholder="昵称" />
    </div>
    <DialogFooter>
      <DialogClose asChild><Button variant="outline">取消</Button></DialogClose>
      <Button type="submit">保存</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>
DialogTitle 不能省

即使视觉上不想显示标题,也要提供 DialogTitle(可用 sr-only 隐藏),否则屏幕阅读器无法宣告弹窗用途,Radix 也会在控制台警告。

7.4 AlertDialog:破坏性确认

import {
  AlertDialog, AlertDialogAction, AlertDialogCancel,
  AlertDialogContent, AlertDialogDescription, AlertDialogFooter,
  AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger,
} from "@/components/ui/alert-dialog";

<AlertDialog>
  <AlertDialogTrigger asChild><Button variant="destructive">删除账户</Button></AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>确定删除?</AlertDialogTitle>
      <AlertDialogDescription>此操作不可撤销。</AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>取消</AlertDialogCancel>
      <AlertDialogAction onClick={handleDelete}>确认删除</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

7.5 Popover:可交互浮层

import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

<Popover>
  <PopoverTrigger asChild><Button variant="outline">设置宽度</Button></PopoverTrigger>
  <PopoverContent className="w-80">
    <div className="grid gap-2">
      <Label>宽度</Label><Input defaultValue="100%" />
    </div>
  </PopoverContent>
</Popover>

7.6 Tooltip:只读提示

import {
  Tooltip, TooltipContent, TooltipTrigger, TooltipProvider,
} from "@/components/ui/tooltip";

<TooltipProvider>
  <Tooltip>
    <TooltipTrigger asChild><Button variant="ghost" size="icon"><Info /></Button></TooltipTrigger>
    <TooltipContent>更多信息</TooltipContent>
  </Tooltip>
</TooltipProvider>
Tooltip 不承载关键信息

触屏无 hover、键盘用户可能错过,Tooltip 只放"锦上添花"的辅助说明。关键操作说明应直接可见,或改用 Popover。

7.7 DropdownMenu:按钮菜单

import {
  DropdownMenu, DropdownMenuContent, DropdownMenuItem,
  DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger,
  DropdownMenuCheckboxItem,
} from "@/components/ui/dropdown-menu";

<DropdownMenu>
  <DropdownMenuTrigger asChild><Button variant="outline">操作</Button></DropdownMenuTrigger>
  <DropdownMenuContent align="end">
    <DropdownMenuLabel>我的账户</DropdownMenuLabel>
    <DropdownMenuSeparator />
    <DropdownMenuItem>个人资料</DropdownMenuItem>
    <DropdownMenuCheckboxItem checked>显示邮箱</DropdownMenuCheckboxItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem className="text-destructive">退出登录</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

7.8 ContextMenu:右键菜单

import {
  ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger,
} from "@/components/ui/context-menu";

<ContextMenu>
  <ContextMenuTrigger className="flex h-40 items-center justify-center border rounded-md">
    右键点这里
  </ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuItem>复制</ContextMenuItem>
    <ContextMenuItem>粘贴</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>

7.9 受控与嵌套注意

// 受控:自己管理 open 状态(如提交成功后关闭)
const [open, setOpen] = useState(false);
<Dialog open={open} onOpenChange={setOpen}> ... </Dialog>

// 提交成功后 setOpen(false)
组件触发模态可交互内容典型场景
Dialog点击表单/详情
AlertDialog点击是(强)仅按钮删除确认
Popover点击小面板
Tooltiphover/focus提示
DropdownMenu点击菜单项操作菜单
ContextMenu右键菜单项上下文

7.10 小结与下一步