在这里点击右键
'use client'
import { useState } from 'react'
import { Copy, FolderInput, Pencil, Trash2 } from 'lucide-react'
import {
Center,
ContextMenu,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuSub,
Stack,
Text,
} from '@hina-ui/react'
export default function Demo() {
const [last, setLast] = useState('')
return (
<Stack gap="sm" align="stretch" className="w-96">
<ContextMenu
label="文件操作"
content={
<>
<ContextMenuItem icon={<Pencil />} onSelect={() => setLast('重命名')}>
重命名
</ContextMenuItem>
<ContextMenuItem icon={<Copy />} onSelect={() => setLast('复制')}>
复制
</ContextMenuItem>
<ContextMenuSub label="移动到" icon={<FolderInput />}>
<ContextMenuItem onSelect={() => setLast('移动到收藏夹')}>收藏夹</ContextMenuItem>
<ContextMenuItem onSelect={() => setLast('移动到归档')}>归档</ContextMenuItem>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem tone="danger" icon={<Trash2 />} onSelect={() => setLast('删除')}>
删除
</ContextMenuItem>
</>
}
>
<Center className="bg-inset h-32 rounded-lg border border-dashed select-none">
<Text tone="muted" size="sm">
在这里点击右键
</Text>
</Center>
</ContextMenu>
{last && (
<Text tone="muted" size="sm">
选择了:{last}
</Text>
)}
</Stack>
)
}
tsx
用法
import { ContextMenu, ContextMenuItem } from '@hina-ui/react'
ts
children 是响应右键的区域,content 属性是菜单里的条目。在区域内点击右键,或者在触屏上长按,菜单在指针的位置展开,选中条目后自动收起。条目、多选项、单选项、分组、标题、分隔线与子菜单都与 DropdownMenu 同一套,只是名字换成 ContextMenu 开头。
在这里点击右键
import { Center, ContextMenu, ContextMenuItem, Text } from '@hina-ui/react'
export default function Demo() {
return (
<ContextMenu
label="图片操作"
content={
<>
<ContextMenuItem>在新标签页打开</ContextMenuItem>
<ContextMenuItem>复制图片地址</ContextMenuItem>
<ContextMenuItem>保存图片</ContextMenuItem>
</>
}
>
<Center className="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
<Text tone="muted" size="sm">
在这里点击右键
</Text>
</Center>
</ContextMenu>
)
}
tsx
示例
条目
条目的 icon 属性用于前置图标,trailing 属性用于尾部内容,删除这类不可撤销的操作设置 tone="danger";ContextMenuLabel 是不可选中的标题,ContextMenuSeparator 画一条分隔线。
在这里点击右键
import { Copy, Pencil, Trash2 } from 'lucide-react'
import {
Center,
ContextMenu,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
Kbd,
Text,
} from '@hina-ui/react'
export default function Demo() {
return (
<ContextMenu
label="条目操作"
content={
<>
<ContextMenuLabel>这条笔记</ContextMenuLabel>
<ContextMenuItem icon={<Pencil />} trailing={<Kbd>E</Kbd>}>
编辑
</ContextMenuItem>
<ContextMenuItem icon={<Copy />} trailing={<Kbd>⌘C</Kbd>}>
复制
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem tone="danger" icon={<Trash2 />}>
删除
</ContextMenuItem>
</>
}
>
<Center className="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
<Text tone="muted" size="sm">
在这里点击右键
</Text>
</Center>
</ContextMenu>
)
}
tsx
多选项
ContextMenuCheckboxItem 用于可以同时选中多项的开关,checked 可受控,切换后菜单保持展开。
网格开,标尺关
'use client'
import { useState } from 'react'
import { Center, ContextMenu, ContextMenuCheckboxItem, Text } from '@hina-ui/react'
export default function Demo() {
const [grid, setGrid] = useState(true)
const [ruler, setRuler] = useState(false)
return (
<ContextMenu
label="视图"
content={
<>
<ContextMenuCheckboxItem checked={grid} onCheckedChange={setGrid}>
显示网格
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem checked={ruler} onCheckedChange={setRuler}>
显示标尺
</ContextMenuCheckboxItem>
</>
}
>
<Center className="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
<Text tone="muted" size="sm">
{`网格${grid ? '开' : '关'},标尺${ruler ? '开' : '关'}`}
</Text>
</Center>
</ContextMenu>
)
}
tsx
单选项
一组互斥的选项用 ContextMenuRadioGroup 包裹,当前项自动带选中标记。
按名称排序
'use client'
import { useState } from 'react'
import {
Center,
ContextMenu,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
Text,
} from '@hina-ui/react'
const names: Record<string, string> = { name: '名称', date: '修改日期', size: '大小' }
export default function Demo() {
const [sort, setSort] = useState('name')
return (
<ContextMenu
label="排序"
content={
<>
<ContextMenuLabel>排序方式</ContextMenuLabel>
<ContextMenuRadioGroup value={sort} onValueChange={setSort}>
<ContextMenuRadioItem value="name">名称</ContextMenuRadioItem>
<ContextMenuRadioItem value="date">修改日期</ContextMenuRadioItem>
<ContextMenuRadioItem value="size">大小</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</>
}
>
<Center className="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
<Text tone="muted" size="sm">
{`按${names[sort]}排序`}
</Text>
</Center>
</ContextMenu>
)
}
tsx
不可用
disabled 让整块区域不再响应右键,浏览器自己的菜单照常出现;条目上的 disabled 只让那一条不可选。
在这里点击右键
'use client'
import { useState } from 'react'
import { Center, ContextMenu, ContextMenuItem, Stack, Switch, Text } from '@hina-ui/react'
export default function Demo() {
const [locked, setLocked] = useState(false)
return (
<Stack gap="md" align="start">
<Switch checked={locked} onCheckedChange={setLocked}>
锁定区域
</Switch>
<ContextMenu
label="条目操作"
disabled={locked}
content={
<>
<ContextMenuItem>编辑</ContextMenuItem>
<ContextMenuItem disabled>移动</ContextMenuItem>
<ContextMenuItem tone="danger">删除</ContextMenuItem>
</>
}
>
<Center className="bg-inset h-32 w-96 rounded-lg border border-dashed select-none">
<Text tone="muted" size="sm">
{locked ? '已锁定,右键是浏览器自己的菜单' : '在这里点击右键'}
</Text>
</Center>
</ContextMenu>
</Stack>
)
}
tsx
行为
- 菜单在指针的位置展开,靠近视口边缘时向内翻转。
- 菜单打开期间页面停止滚动;点击菜单外部、按 Esc 或者选中条目都会收起。
- 方向键在条目间移动,回车或者空格选中,输入字母跳到匹配的条目。
- 触屏上长按区域打开菜单。
无障碍
- 菜单是
role="menu",用label给它一个名称;条目分别是menuitem、menuitemcheckbox与menuitemradio。 - 右键菜单里的操作应当在页面上另有可达的入口,键盘与辅助技术的用户未必能触发它。
API
ContextMenu
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
label | string | — | 菜单的无障碍名称 |
disabled | boolean | false | 区域是否不再响应右键 |
open | boolean | — | 是否打开,可受控 |
className | string | — | 追加至菜单面板的类名 |
| 属性 | 说明 |
|---|---|
children | 响应右键的区域 |
content | 菜单里的条目 |
条目与其他子件
ContextMenuItem、ContextMenuCheckboxItem、ContextMenuRadioGroup、ContextMenuRadioItem、ContextMenuGroup、ContextMenuLabel、ContextMenuSeparator 与 ContextMenuSub 的属性与回调与 DropdownMenu 的同名子件完全一致,见 DropdownMenu。