# ContextMenu

A menu that opens at the pointer on right click, on the keyboard context menu keys, or on a touch long press over an area, with the same items, groups, checkbox and radio items, shortcuts and destructive style as DropdownMenu. Every action in it must also be reachable another way.

## Cách dùng

### Khi nào dùng

- Offer the same actions as a visible menu when a user right-clicks, uses a menu key or long-presses an item.

### Khi nào không dùng

- The only way to reach an action. Keep the same actions available elsewhere.
- An action menu on touch without a long-press affordance. Use DropdownMenu.

## Ví dụ

### File row

```tsx
<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>
```

### Checkbox options

```tsx
<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuLabel>View</ContextMenuLabel>
      <ContextMenuCheckboxItem checked={menuPinned} onCheckedChange={setMenuPinned}>Pin to top</ContextMenuCheckboxItem>
      <ContextMenuSeparator />
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>
```

### Radio options

```tsx
<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuLabel>Sort by</ContextMenuLabel>
      <ContextMenuRadioGroup value={menuSort} onValueChange={setMenuSort}>
        <ContextMenuRadioItem value="name">Name</ContextMenuRadioItem>
        <ContextMenuRadioItem value="date">Date</ContextMenuRadioItem>
      </ContextMenuRadioGroup>
      <ContextMenuSeparator />
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>
```

### Disabled area

```tsx
<div className="flex w-64 max-w-full flex-col gap-md">
  <div role="group" aria-label="File actions" className="flex gap-sm">
    <Button size="sm" variant="outline">Rename</Button>
    <Button size="sm" variant="outline">Delete</Button>
  </div>
  <ContextMenu>
    <ContextMenuTrigger disabled className="rounded-container border border-dashed border-border px-lg py-xl" aria-label="More actions for report.pdf">
      <p className="text-sm font-medium">report.pdf</p>
      <p className="text-xs text-text-secondary">Right click, long press or Shift+F10</p>
    </ContextMenuTrigger>
    <ContextMenuContent aria-label="File actions">
      <ContextMenuItem leadingIcon={<IconPencil size={16} />} shortcut="F2">Rename</ContextMenuItem>
      <ContextMenuItem disabled>Duplicate</ContextMenuItem>
      <ContextMenuSeparator />
      <ContextMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</ContextMenuItem>
    </ContextMenuContent>
  </ContextMenu>
</div>
```

## Nên và không nên

### Nên

- Mirror a visible menu or toolbar; the context menu is a shortcut.
- Use the same item names as the visible menu.
- Provide an accessible name on ContextMenuContent when the trigger label alone does not explain the actions. ContextMenuTrigger adds tabIndex 0 unless disabled.

### Không nên

- Do not make it the only way to an action.
- Do not override the browser menu on text inputs or links.

## Props

| Tên | Kiểu | Giá trị | Mặc định | Mô tả |
| --- | --- | --- | --- | --- |
| `onOpenChange` | `(open: boolean) => void` |  |  | Called when it opens or closes. |
| `modal` | `boolean` |  | `true` | Block outside interaction while open. |
| `disabled` | `boolean` |  | `false` | On ContextMenuTrigger: bypasses the custom menu and preserves the browser menu. On item parts: prevents selection. |
| `asChild` | `boolean` |  | `false` | On ContextMenuTrigger or ContextMenuItem: use the only child as the rendered element. Interactive links and text fields keep their native context menu. |
| `longPressDelay` | `number` |  | `500` | Milliseconds a touch must be held before the menu opens. |
| `loop` | `boolean` |  | `false` | On ContextMenuContent: wrap arrow-key navigation from the last enabled item to the first. |
| `avoidCollisions` | `boolean` |  | `true` | On ContextMenuContent: shift or flip the menu to keep it inside the viewport. |
| `collisionPadding` | `number \| Partial<Record<"top" \| "right" \| "bottom" \| "left", number>>` |  | `8` | On ContextMenuContent: minimum distance from viewport edges in px. |
| `onSelect` | `(event: Event) => void` |  |  | On ContextMenuItem: runs when selected by pointer or keyboard. Call event.preventDefault() to keep the menu open. |
| `destructive` | `boolean` |  | `false` | On ContextMenuItem: danger text and icon for a destructive action. |
| `inset` | `boolean` |  | `false` | On ContextMenuItem and ContextMenuLabel: align the text with rows that contain icons or selection indicators. |
| `leadingIcon` | `node` |  |  | On ContextMenuItem: decorative 16px icon. Ignored with asChild. |
| `shortcut` | `string` |  |  | On ContextMenuItem: a displayed key hint; the application handles the shortcut. |
| `checked` | `boolean \| "indeterminate"` |  |  | On ContextMenuCheckboxItem: controlled check state. |
| `onCheckedChange` | `(checked: boolean) => void` |  |  | On ContextMenuCheckboxItem: receives the next check state. |
| `value` | `string` |  |  | On ContextMenuRadioGroup: the selected value. On ContextMenuRadioItem: the required option identifier. |
| `onValueChange` | `(value: string) => void` |  |  | On ContextMenuRadioGroup: receives the selected radio item value. |

### Slot

| Tên | Bắt buộc | Mô tả |
| --- | --- | --- |
| `children` | có | ContextMenuTrigger and ContextMenuContent with item parts. |

## Cấu tạo

| Phần | Mô tả |
| --- | --- |
| `root` | ContextMenu: holds open state. |
| `trigger` | ContextMenuTrigger: the area that listens for right click. |
| `content` | ContextMenuContent: the same floating list as DropdownMenuContent, positioned at the pointer or trigger. |
| `item` | ContextMenuItem: same as DropdownMenuItem. |
| `icon` | The leading icon of an item, as in DropdownMenu. |
| `destructive` | A destructive ContextMenuItem, as in DropdownMenu. |
| `destructiveIcon` | The leading icon of a destructive item, in the danger text color. |
| `indicator` | The check or radio dot of a checkbox or radio item, as in DropdownMenu. |
| `groupLabel` | ContextMenuLabel: a group heading, as in DropdownMenu. |
| `separator` | ContextMenuSeparator: a line between groups, as in DropdownMenu. |
| `shortcut` | ContextMenuShortcut: the shortcut hint, as in DropdownMenu. |

## Khả năng truy cập

Role: `menu`

| Phím | Hành động |
| --- | --- |
| `Shift+F10 / ContextMenu key` | Opens the menu for the focused trigger area. |
| `ArrowDown / ArrowUp` | Moves between items. |
| `Enter / Space` | Activates the item. |
| `Escape` | Closes the menu and returns focus to the trigger area. |
| `Type a letter` | Moves to the next item starting with it. |

- Items are the DropdownMenu item parts (shared styles and keyboard), exported under ContextMenu names.
- The trigger area must be focusable (tabIndex 0 with an accessible name) for keyboard users.
- The menu is positioned at the pointer, or at the trigger's start when opened from the keyboard.
- Touch: a long press (about 500ms without moving more than a few px) opens the menu at the touch point. The component implements it itself because iOS Safari does not fire contextmenu on long press. If a native contextmenu event follows the long press, it is ignored so the menu does not open twice.
- Mouse and keyboard stay for devices with them attached: right click, Shift+F10 and the Menu key.
- The trigger area sets -webkit-touch-callout: none and user-select: none so the browser callout and text selection do not fight the long press.
- Submenu parts are not exported. Use groups and separators for sections within this menu.

## Cài đặt

```bash
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](https://werewolf-ui.vercel.app/vi/docs/installation.md) để biết yêu cầu và cách cài qua registry.
