shadcn-vue Context Menu 组件完整指南:从安装到源码级解析
【免费下载链接】shadcn-vueVue port of shadcn-ui项目地址: https://gitcode.com/gh_mirrors/sh/shadcn-vue
本文围绕 shadcn-vue(Vue 版 shadcn-ui)中的 Context Menu(右键菜单)组件展开,介绍其基于 reka-ui 的架构设计、CLI 与手动两种安装方式、从基础用法到复选框/单选框/子菜单的完整实战,并结合仓库源码剖析每个子组件的样式实现与属性透传原理。阅读完本文,你将能够独立在 Vue 3 + Tailwind CSS 项目中搭建功能完整、样式可定制的右键上下文菜单。
组件概览:Context Menu 能做什么
Context Menu 是一个由用户右键(或长按)触发、用于展示一组操作或功能的菜单组件。它是桌面应用交互模式在 Web 端的重要移植:用户不需要预先看到所有按钮,而是通过右键唤起与当前元素上下文相关的动作集合(如"复制 / 粘贴 / 删除")。
在 shadcn-vue 中,该组件以 reka-ui 的ContextMenu原语为核心,通过一层薄封装提供开箱即用的 Tailwind 样式。完整的组件族位于 apps/v4/registry/new-york-v4/ui/context-menu,共 15 个 Vue 组件加一个统一出口:
| 组件 | 作用 |
|---|---|
ContextMenu | 根组件,管理打开状态与交互行为 |
ContextMenuTrigger | 右键触发区域 |
ContextMenuContent | 菜单弹出内容面板 |
ContextMenuItem | 普通菜单项(支持inset、variant="destructive") |
ContextMenuCheckboxItem | 带复选框的菜单项 |
ContextMenuRadioGroup/ContextMenuRadioItem | 单选框分组与单选菜单项 |
ContextMenuSub/ContextMenuSubTrigger/ContextMenuSubContent | 子菜单(二级菜单) |
ContextMenuLabel | 菜单分组标签 |
ContextMenuSeparator | 分隔线 |
ContextMenuShortcut | 快捷键提示文本 |
ContextMenuGroup | 菜单项逻辑分组 |
ContextMenuPortal | 将内容传送到 body 的传送门 |
所有组件通过 index.ts 统一导出,方便从@/components/ui/context-menu一处引入。
安装
方式一:CLI 一键安装(推荐)
在项目根目录执行:
npx shadcn-vue@latest add context-menuCLI 会自动将上述 15 个组件文件写入你的components/ui/context-menu目录,并确保reka-ui等依赖就绪。该命令由仓库中 packages/cli 的 add 命令体系驱动,自动处理依赖安装与文件落盘。
方式二:手动安装
- 安装底层依赖 reka-ui:
npm install reka-ui- 从仓库 apps/v4/registry/new-york-v4/ui/context-menu 将组件源码复制到你的项目中(如
src/components/ui/context-menu); - 更新导入路径以匹配你的项目结构(例如将
@/registry/new-york-v4/ui/context-menu改为@/components/ui/context-menu),并确认@/lib/utils中的cn工具函数可用。
基础用法
以下是最小可用的右键菜单示例(即官方文档 context-menu.md 中的核心用法):
<script setup lang="ts"> import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from '@/components/ui/context-menu' </script> <template> <ContextMenu> <ContextMenuTrigger>Right click</ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem>Profile</ContextMenuItem> <ContextMenuItem>Billing</ContextMenuItem> <ContextMenuItem>Team</ContextMenuItem> <ContextMenuSeparator /> <ContextMenuItem>Subscription</ContextMenuItem> </ContextMenuContent> </ContextMenu> </template>结构非常清晰:ContextMenuTrigger包裹可右键的目标元素,ContextMenuContent内部放置菜单项。你可以在ContextMenuContent上通过class属性直接覆盖宽度等样式(如class="w-52")。
源码级解析:组件如何工作
shadcn-vue 的 Context Menu 全部采用"reka-ui 原语 + 样式封装"模式:每个组件只做两件事——把 props/emits 透传给 reka-ui 对应原语,并附加data-slot标识与 Tailwind 样式类。
根组件与触发器的状态管理
ContextMenu.vue 直接包装ContextMenuRoot,使用useForwardPropsEmits将外部传入的 props 与 emits 全部转发:
const props = defineProps<ContextMenuRootProps>() const emits = defineEmits<ContextMenuRootEmits>() const forwarded = useForwardPropsEmits(props, emits)这意味着 reka-ui 提供的modal、dir、open、defaultOpen、onOpenChange、delayDuration等能力均可用(具体 API 以 reka-ui 文档为准)。ContextMenuTrigger.vue 同样通过useForwardProps透传,负责捕获右键事件并定位弹出位置。
内容面板:定位、尺寸与动效
ContextMenuContent.vue 是样式最丰富的文件。它先用reactiveOmit(props, "class")剔除class避免污染透传,再通过ContextMenuPortal将菜单渲染到 body 层级,避免父容器overflow裁剪:
<ContextMenuPortal> <ContextMenuContent >const props = withDefaults(defineProps<ContextMenuItemProps & { class?: HTMLAttributes["class"] inset?: boolean variant?: "default" | "destructive" }>(), { variant: "default", })inset:缩进模式(pl-8),用于与带图标的菜单项对齐,源码通过:data-inset配合data-[inset]:pl-8实现;variant="destructive":危险操作样式,data-[variant=destructive]:text-destructive-foreground及聚焦时的focus:bg-destructive/10(暗色模式dark:focus:bg-destructive/40);- 图标处理:
[&_svg:not([class*='text-'])]:text-muted-foreground统一图标颜色,[&_svg:not([class*='size-'])]:size-4统一图标尺寸; - 禁用态:继承 reka-ui 的
disabled,配合data-[disabled]:pointer-events-none><span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <ContextMenuItemIndicator> <slot name="indicator-icon"> <Check class="size-4" /> </slot> </ContextMenuItemIndicator> </span>使用方式:
ContextMenuCheckboxItem通过v-model(model-value)控制勾选;ContextMenuRadioGroup通过model-value管理单选值,内部放置多个ContextMenuRadioItem。子菜单
子菜单由三件套组成:ContextMenuSub.vue(状态容器)、ContextMenuSubTrigger.vue(右侧带
ChevronRight箭头,ml-auto右对齐,展开时data-[state=open]:bg-accent)、ContextMenuSubContent.vue(二级面板,shadow-lg且使用origin-(--reka-context-menu-content-transform-origin)让缩放动画以触发器为原点)。从源码可见,ContextMenuSubContent的 props 类型复用自DropdownMenuSubContentProps,与下拉菜单共享同一套接口。其余小部件
- ContextMenuSeparator.vue:
bg-border -mx-1 my-1 h-px的水平分隔线; ContextMenuLabel、ContextMenuGroup、ContextMenuPortal、ContextMenuShortcut:均为轻量透传封装,分别提供分组标题、逻辑分组、传送门与快捷键提示(快捷键通常显示为右侧的⌘[等文字)。
完整实战:仿浏览器右键菜单
仓库中的官方演示 ContextMenuDemo.vue 集合了上述全部特性,几乎等同于浏览器原生右键菜单:普通项、禁用项、子菜单、复选框、单选框、快捷键、图标与危险操作一应俱全。核心结构如下:
<script setup lang="ts"> import { Code2Icon, PlusIcon, TrashIcon } from '@lucide/vue' import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from '@/registry/new-york-v4/ui/context-menu' </script> <template> <ContextMenu> <ContextMenuTrigger class="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm"> Right click here </ContextMenuTrigger> <ContextMenuContent class="w-52"> <!-- 普通项 + 快捷键 + 禁用项 --> <ContextMenuItem inset> Back <ContextMenuShortcut>⌘[</ContextMenuShortcut> </ContextMenuItem> <ContextMenuItem inset disabled> Forward <ContextMenuShortcut>⌘]</ContextMenuShortcut> </ContextMenuItem> <!-- 子菜单 --> <ContextMenuSub> <ContextMenuSubTrigger inset> More Tools </ContextMenuSubTrigger> <ContextMenuSubContent class="w-44"> <ContextMenuItem inset>Save Page... <ContextMenuShortcut>⇧⌘S</ContextMenuShortcut></ContextMenuItem> <ContextMenuItem><PlusIcon /> Create Shortcut...</ContextMenuItem> <ContextMenuSeparator /> <ContextMenuItem variant="destructive"><TrashIcon /> Delete</ContextMenuItem> </ContextMenuSubContent> </ContextMenuSub> <!-- 复选框 --> <ContextMenuCheckboxItem :model-value="true"> Show Bookmarks <ContextMenuShortcut>⌘⇧B</ContextMenuShortcut> </ContextMenuCheckboxItem> <ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem> <!-- 单选框 --> <ContextMenuRadioGroup model-value="pedro"> <ContextMenuLabel inset>People</ContextMenuLabel> <ContextMenuRadioItem value="pedro">Pedro Duarte</ContextMenuRadioItem> <ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem> </ContextMenuRadioGroup> </ContextMenuContent> </ContextMenu> </template>实战要点总结:
- 触发区域不限于文本,可以是一个带虚线边框的占位卡片(如演示中的
h-[150px] w-[300px]区域),任意元素都能成为右键目标; - 图标通过默认插槽放在文本前,组件内置样式会自动统一图标尺寸与颜色;
disabled项不响应点击并自动半透明;- 复选框默认选中只需传
:model-value="true",单选组通过model-value指定当前值; - 需要二级菜单时,把
ContextMenuSub当作普通菜单项平级放置即可,子菜单内容宽度可通过ContextMenuSubContent的class单独控制(如w-44)。
进阶说明与注意事项
- 导入路径:演示代码从
@/registry/new-york-v4/ui/context-menu引入(这是站点自身目录),你安装后应使用@/components/ui/context-menu或你的实际别名路径;手动安装时记得同步修改 index.ts 中各组件的相对导入。 - Props/Emits 透传:所有组件都通过
useForwardProps/useForwardPropsEmits把 reka-ui 的能力原样暴露,因此 reka-ui ContextMenu 原语支持的高级交互(模态行为、方向、受控打开状态等)在本组件中同样可用,不必改源码。 - Portal 渲染:
ContextMenuContent默认经由ContextMenuPortal渲染到 body,可避免父级overflow或z-index上下文导致菜单被裁剪或遮挡;z-50保证了较高的层叠优先级。 - 无障碍与键盘:组件继承 reka-ui 的完整无障碍实现(方向键导航、Enter 选择、Esc 关闭、焦点管理),无需额外处理。
- 样式定制:所有视觉细节均由 Tailwind 类控制,直接传入
class即可覆盖;主题色依赖项目中的popover、accent、destructive等设计令牌,请在主题配置中确保这些色板存在。
相关资源
- 官方组件文档:apps/v4/content/docs/components/context-menu.md
- 组件源码目录:apps/v4/registry/new-york-v4/ui/context-menu
- 完整演示组件:apps/v4/components/demo/ContextMenuDemo.vue
- 组件清单与文档导航:apps/v4/content/docs/02.components.md
【免费下载链接】shadcn-vueVue port of shadcn-ui
项目地址: https://gitcode.com/gh_mirrors/sh/shadcn-vue
- ContextMenuSeparator.vue:
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考