Animated Context Menu
在指针位置打开菜单,并可组合复选项、单选项和子菜单。
适用场景
- 当界面需要以下行为时使用 Animated Context Menu:在指针位置打开菜单,并可组合复选项、单选项和子菜单。
安装
选择安装方式,用法示例将同步更新导入路径。
$ bunx --bun shadcn-vue@2.8.2 add https://orvui.efu.me/r/context-menu.json
首次使用时,将安装的 src/components/orvui/styles.css 导入项目样式入口。
示例
预览
packages/vue/src/motion/context-menu/demo.vue
<script setup lang="ts">
import { ref } from "vue";
import { Check, Copy, Download, Eye, Pencil, Trash2 } from "lucide-vue-next";
import { AnimatePresence, motion } from "motion-v";
import {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuLabel,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuCheckboxItem,
ContextMenuShortcut,
} from "@/components/orvui/motion/context-menu";
import { SPRING_SWAP } from "@/components/orvui/lib/ease";
import { useReducedMotion } from "@/components/orvui/lib/composables";
const message = ref<string | null>(null);
const offline = ref(false), reduce = useReducedMotion();
const items = [
["Open", Eye, "Folder opened", "↵"],
["Rename", Pencil, "Ready to rename", "R"],
["Duplicate", Copy, "Folder duplicated", "⌘D"],
["Download", Download, "Download started", undefined],
].map(([label, icon, message, shortcut]) => ({ label: label as string, icon, message: message as string, shortcut: shortcut as string | undefined }));
function checked(value: boolean) { offline.value = value; message.value = value ? "Available offline" : "Online only"; }
</script>
<template>
<div class="flex min-h-[360px] w-full items-center justify-center">
<ContextMenu
><ContextMenuTrigger
><button
type="button"
class="group flex flex-col items-center outline-none"
>
<div
class="relative h-24 w-32 transition-transform duration-150 group-active:scale-[0.98] group-focus-visible:rounded-2xl group-focus-visible:ring-2 group-focus-visible:ring-foreground/20 group-focus-visible:ring-offset-4 group-focus-visible:ring-offset-background"
>
<div
class="absolute left-1 top-1 h-7 w-14 rounded-t-[10px] bg-[#d4a84f] dark:bg-[#a77d2f]"
/>
<div
class="absolute inset-x-0 bottom-0 top-5 rounded-[14px] bg-[#e7bb61] shadow-[0_14px_24px_-16px_rgba(90,58,8,0.75)] dark:bg-[#bd8d36]"
/>
<div
class="absolute inset-x-0 bottom-0 top-9 rounded-[14px] bg-[#efc86f] dark:bg-[#cb9a41]"
/>
<div
class="absolute inset-x-5 bottom-4 h-px bg-black/10 dark:bg-white/10"
/>
</div>
<span class="mt-4 text-sm font-medium text-foreground"
>Right click on me</span
>
<div class="mt-1 h-4">
<AnimatePresence mode="wait" :initial="false"
><motion.span
v-if="message"
:key="message"
:initial="
reduce
? { opacity: 0 }
: { opacity: 0, y: 3, filter: 'blur(2px)' }
"
:animate="{ opacity: 1, y: 0, filter: 'blur(0px)' }"
:exit="
reduce
? { opacity: 0 }
: { opacity: 0, y: -2, filter: 'blur(2px)' }
"
:transition="reduce ? { duration: 0.1 } : SPRING_SWAP"
class="flex items-center gap-1 text-[10px] text-muted-foreground"
><Check aria-hidden="true" class="h-3 w-3 text-success" />{{
message
}}</motion.span
><motion.span v-else key="hint" class="text-[10px] text-muted-foreground"
>or long-press · Shift + F10</motion.span
></AnimatePresence
>
</div>
</button></ContextMenuTrigger
><ContextMenuContent ariaLabel="Folder actions" class="w-60"
><ContextMenuLabel>Project files</ContextMenuLabel
><ContextMenuItem
v-for="item in items"
:key="item.label"
:text-value="item.label"
@select="message = item.message"
><component :is="item.icon" aria-hidden="true" class="h-4 w-4" />{{
item.label
}}<ContextMenuShortcut v-if="item.shortcut">{{
item.shortcut
}}</ContextMenuShortcut></ContextMenuItem
><ContextMenuSeparator /><ContextMenuCheckboxItem
text-value="Keep offline"
:model-value="offline"
:close-on-select="false"
@update:model-value="checked"
>Keep offline</ContextMenuCheckboxItem
><ContextMenuSeparator /><ContextMenuItem
tone="destructive"
text-value="Move to trash"
@select="message = 'Moved to trash'"
><Trash2 aria-hidden="true" class="h-4 w-4" />Move to
trash<ContextMenuShortcut>⌘⌫</ContextMenuShortcut></ContextMenuItem
></ContextMenuContent
></ContextMenu
>
</div>
</template>
packages/vue/src/motion/context-menu/src/ContextMenu.vue
状态与事件
需要由表单、路由或父组件管理时,绑定下列状态并接收对应更新。v-model 是属性与 update 事件的简写;值的类型应与 API 一致。
| 组件 | Vue 绑定 | 更新事件 |
|---|---|---|
ContextMenu | v-model:openboolean | update:open |
ContextMenuCheckboxItem | v-modelboolean | update:modelValue |
ContextMenuCheckboxItem | v-model:checkedboolean | update:checked |
可选受控属性未传入时,按组件文档的默认行为工作;不要用固定的 false 或空值代替未传入。异步请求和持久化在应用中处理,再把结果传回组件。
API 参考
ContextMenu
open?booleanundefineddefaultOpen?booleanfalse事件
update:open—@update:open插槽
default() => unknown#defaultContextMenuTrigger
disabled?booleanfalse插槽
default() => unknown#defaultContextMenuContent
ariaLabel?string"Context menu"插槽
default() => unknown#defaultContextMenuLabel
inset?booleanfalse插槽
default() => unknown#defaultContextMenuItem
disabled?booleanfalsecloseOnSelect?booleantruetone?"default" | "destructive"—inset?booleanfalsetextValue?string—事件
select—@select插槽
default() => unknown#defaultContextMenuCheckboxItem
modelValue?booleanundefinedchecked?booleanundefineddisabled?booleanfalsecloseOnSelect?booleantruetone?"default" | "destructive"—inset?booleanfalsetextValue?string—事件
update:modelValue—@update:modelValueupdate:checked—@update:checkedchange—@change插槽
default() => unknown#defaultContextMenuShortcut
插槽
default() => unknown#default无障碍
为交互控件提供明确的无障碍名称。保留文档中的键盘路径、可见焦点与减少动态效果状态;自定义插槽应继续保留所替代部件的语义。
常见问题
- 如何配置 Animated Context Menu?
- 通过 open、defaultOpen 配置 Animated Context Menu;API 表列出其类型与默认值。
- 如何连接到应用状态?
- 监听 update:open,将组件连接到应用状态。
- 可以在服务端渲染吗?
- 可以在 Vue SSR 应用中正常渲染。服务端与客户端的初始 props 应保持一致;浏览器测量与交互资源在挂载后启动。
更新于
来源与许可 ↗MIT · 0.1.1