Chuyển tới nội dung

Pro đang mở bán trước. Cài đặt mở từ tháng 1/2027. Xem Pro

Component

ContextMenu

Menu mở tại pointer khi nhấn chuột phải, khi dùng các phím context menu của bàn phím, hoặc khi nhấn giữ trên cảm ứng trên một vùng, với cùng các mục, nhóm, mục checkbox và radio, shortcut và kiểu destructive như DropdownMenu. Mọi hành động trong đó cũng phải tới được bằng cách khác.

  • npm: 1 trực tiếp, 5 tổng
  • registry: 3 trực tiếp, 4 tổng

Xem trước

Props

  • disabled
  • longPressDelay
  • loop
  • avoidCollisions
  • items
import { IconCopy, IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="Thao tác tệp" className="flex gap-sm">
    <Button size="sm" variant="outline">Đổi tên</Button>
    <Button size="sm" variant="outline">Xóa</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="flex min-h-28 flex-col justify-center gap-sm rounded-container border border-dashed border-border px-lg py-xl" aria-label="Thêm thao tác cho report.pdf">
      <p className="text-sm font-medium text-text-primary">report.pdf</p>
      <p className="text-xs text-text-secondary">Nhấp chuột phải, nhấn giữ hoặc Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="Thao tác tệp">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Đổi tên</ContextMenuItem>
      <ContextMenuItem disabled leadingIcon={<IconCopy size={16} />}>Nhân bản</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Xóa</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Cách dùng

Khi nào dùng

  • Đưa cùng các hành động như một menu đang hiện khi người dùng nhấn chuột phải, dùng phím menu hoặc nhấn giữ một mục.

Khi nào không dùng

  • Cách duy nhất để tới một hành động. Giữ cùng các hành động ở nơi khác.
  • Menu hành động trên cảm ứng mà không có dấu hiệu nhấn giữ. Dùng DropdownMenu.

Ví dụ

Hàng file

import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="Thao tác tệp" className="flex gap-sm">
    <Button size="sm" variant="outline">Đổi tên</Button>
    <Button size="sm" variant="outline">Xóa</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="Thêm thao tác cho report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Nhấp chuột phải, nhấn giữ hoặc Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="Thao tác tệp">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Nhân bản</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Lựa chọn checkbox

import { useState } from "react"
import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

const [menuPinned, setMenuPinned] = useState(false)

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="Thao tác tệp" className="flex gap-sm">
    <Button size="sm" variant="outline">Đổi tên</Button>
    <Button size="sm" variant="outline">Xóa</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="Thêm thao tác cho report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Nhấp chuột phải, nhấn giữ hoặc Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="Thao tác tệp">
      <ContextMenuLabel>Xem</ContextMenuLabel>
      <ContextMenuCheckboxItem checked={menuPinned} onCheckedChange={setMenuPinned}>Ghim lên đầu</ContextMenuCheckboxItem>
      <ContextMenuSeparator />
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Nhân bản</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Lựa chọn radio

import { useState } from "react"
import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

const [menuSort, setMenuSort] = useState("name")

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="Thao tác tệp" className="flex gap-sm">
    <Button size="sm" variant="outline">Đổi tên</Button>
    <Button size="sm" variant="outline">Xóa</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="Thêm thao tác cho report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Nhấp chuột phải, nhấn giữ hoặc Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="Thao tác tệp">
      <ContextMenuLabel>Sắp xếp theo</ContextMenuLabel>
      <ContextMenuRadioGroup value={menuSort} onValueChange={setMenuSort}>
        <ContextMenuRadioItem value="name">Tên</ContextMenuRadioItem>
        <ContextMenuRadioItem value="date">Ngày</ContextMenuRadioItem>
      </ContextMenuRadioGroup>
      <ContextMenuSeparator />
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Nhân bản</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Vùng vô hiệu

report.pdf

Nhấp chuột phải, nhấn giữ hoặc Shift+F10

import { IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu"

<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="Thao tác tệp" className="flex gap-sm">
    <Button size="sm" variant="outline">Đổi tên</Button>
    <Button size="sm" variant="outline">Xóa</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger disabled className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="Thêm thao tác cho report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Nhấp chuột phải, nhấn giữ hoặc Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="Thao tác tệp">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Nhân bản</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>

Nên và không nên

Nên

  • Cho giống một menu hoặc thanh công cụ đang hiện; context menu là một lối tắt.
  • Dùng cùng tên mục như menu đang hiện.
  • Gắn tên truy cập cho ContextMenuContent khi chỉ label của trigger không giải thích được các hành động. ContextMenuTrigger thêm tabIndex 0 trừ khi disabled.

Không nên

  • Đừng biến nó thành cách duy nhất để tới một hành động.
  • Đừng ghi đè menu của trình duyệt trên input chữ hoặc link.

Props

TênKiểuGiá trịMặc địnhMô tả
onOpenChange(open: boolean) => void--Được gọi khi nó mở hoặc đóng.
modalboolean-trueChặn tương tác bên ngoài khi đang mở.
disabledboolean-falseTrên ContextMenuTrigger: bỏ qua menu tùy chỉnh và giữ menu của trình duyệt. Trên các phần mục: ngăn việc chọn.
asChildboolean-falseTrên ContextMenuTrigger hoặc ContextMenuItem: dùng phần tử con duy nhất làm phần tử được render. Link tương tác và field chữ giữ context menu native của chúng.
longPressDelaynumber-500Số mili giây một lần chạm phải được giữ trước khi menu mở.
loopboolean-falseTrên ContextMenuContent: quay vòng điều hướng bằng phím mũi tên từ mục enabled cuối về mục đầu.
avoidCollisionsboolean-trueTrên ContextMenuContent: dịch hoặc lật menu để giữ nó trong viewport.
collisionPaddingnumber | Partial<Record<"top" | "right" | "bottom" | "left", number>>-8Trên ContextMenuContent: khoảng cách tối thiểu tới mép viewport, tính bằng px.
onSelect(event: Event) => void--Trên ContextMenuItem: chạy khi được chọn bằng pointer hoặc bàn phím. Gọi event.preventDefault() để giữ menu mở.
destructiveboolean-falseTrên ContextMenuItem: chữ và icon danger cho hành động destructive.
insetboolean-falseTrên ContextMenuItem và ContextMenuLabel: căn chữ với các hàng có icon hoặc dấu chọn.
leadingIconnode--Trên ContextMenuItem: icon trang trí 16px. Bị bỏ qua khi có asChild.
shortcutstring--Trên ContextMenuItem: gợi ý phím được hiện; ứng dụng xử lý shortcut.
checkedboolean | "indeterminate"--Trên ContextMenuCheckboxItem: trạng thái check được kiểm soát.
onCheckedChange(checked: boolean) => void--Trên ContextMenuCheckboxItem: nhận trạng thái check kế tiếp.
valuestring--Trên ContextMenuRadioGroup: value đang được chọn. Trên ContextMenuRadioItem: định danh option bắt buộc.
onValueChange(value: string) => void--Trên ContextMenuRadioGroup: nhận value của mục radio được chọn.

Slot

TênBắt buộcMô tả
childrencóContextMenuTrigger và ContextMenuContent với các phần mục.

Cấu tạo

PhầnMô tả
rootContextMenu: giữ trạng thái mở.
triggerContextMenuTrigger: vùng lắng nghe nhấn chuột phải.
contentContextMenuContent: cùng danh sách nổi như DropdownMenuContent, đặt tại pointer hoặc trigger.
itemContextMenuItem: giống DropdownMenuItem.
iconIcon đầu của một mục, như trong DropdownMenu.
destructiveMột ContextMenuItem destructive, như trong DropdownMenu.
destructiveIconIcon đầu của mục destructive, bằng màu chữ danger.
indicatorDấu check hoặc chấm radio của mục checkbox hoặc radio, như trong DropdownMenu.
groupLabelContextMenuLabel: tiêu đề nhóm, như trong DropdownMenu.
separatorContextMenuSeparator: một đường giữa các nhóm, như trong DropdownMenu.
shortcutContextMenuShortcut: gợi ý shortcut, như trong DropdownMenu.

Khả năng truy cập

Role: menu

PhímHành động
Shift+F10 / ContextMenu keyMở menu cho vùng trigger đang focus.
ArrowDown / ArrowUpDi chuyển giữa các mục.
Enter / SpaceKích hoạt mục.
EscapeĐóng menu và trả focus về vùng trigger.
Type a letterChuyển tới mục kế tiếp bắt đầu bằng chữ đó.
  • Các mục là các phần mục của DropdownMenu (dùng chung kiểu và bàn phím), được xuất dưới tên ContextMenu.
  • Vùng trigger phải focus được (tabIndex 0 với một tên truy cập) cho người dùng bàn phím.
  • Menu được đặt tại pointer, hoặc tại đầu trigger khi mở từ bàn phím.
  • Cảm ứng: nhấn giữ (khoảng 500ms, không di chuyển quá vài px) thì mở menu tại điểm chạm. Component tự làm việc này vì iOS Safari không phát contextmenu khi nhấn giữ. Nếu một sự kiện contextmenu native theo sau lần nhấn giữ, sự kiện đó bị bỏ qua để menu không mở hai lần.
  • Chuột và bàn phím vẫn dùng được trên thiết bị có gắn chúng: nhấn chuột phải, Shift+F10 và phím Menu.
  • Vùng trigger đặt -webkit-touch-callout: none và user-select: none để callout của trình duyệt và việc chọn chữ không tranh với nhấn giữ.
  • Các phần submenu không được xuất. Dùng nhóm và separator cho các phần bên trong menu này.

Cài đặt

npx werewolf-ui add context-menu

Chạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.