# 上下文菜单

显示一个位于指针位置的菜单，由右键单击或长按触发。

## Features

- 支持可配置阅读方向的子菜单。

- 支持项目、标签和项目组。

- 支持可选的不确定状态的可检查项目（单个或多个）。

- 支持模态和非模态模式。

- 自定义侧边、对齐方式、偏移量和碰撞处理。

- 焦点完全管理。

- 全键盘导航。

- 支持类型预判。

- 取消和分层行为高度可定制。

- 在触摸设备上通过长按触发

## [Anatomy](#anatomy)

导入所有部件并将它们组合在一起。

```jsx
import { ContextMenu } from "radix-ui";

export default () => (
  <ContextMenu.Root>
    <ContextMenu.Trigger />

    <ContextMenu.Portal>
      <ContextMenu.Content>
        <ContextMenu.Label />

        <ContextMenu.Item />

        <ContextMenu.Group>
          <ContextMenu.Item />
        </ContextMenu.Group>

        <ContextMenu.CheckboxItem>
          <ContextMenu.ItemIndicator />
        </ContextMenu.CheckboxItem>

        <ContextMenu.RadioGroup>
          <ContextMenu.RadioItem>
            <ContextMenu.ItemIndicator />
          </ContextMenu.RadioItem>
        </ContextMenu.RadioGroup>

        <ContextMenu.Sub>
          <ContextMenu.SubTrigger />

          <ContextMenu.Portal>
            <ContextMenu.SubContent />
          </ContextMenu.Portal>
        </ContextMenu.Sub>

        <ContextMenu.Separator />
      </ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu.Root>
);
```

## [API 参考](#api-参考)

遵循 [菜单 WAI-ARIA 设计模式](https://www.w3.org/WAI/ARIA/apg/patterns/menu) 并使用 [游走的 tabindex](https://www.w3.org/TR/wai-aria-practices-1.2/examples/menu-button/menu-button-actions.html) 来管理菜单项之间的焦点移动。

### [根](#根)

包含上下文菜单的所有部分。

| Prop           | Type       | Default          |
| -------------- | ---------- | ---------------- |
| `dir`          | `enum`     | No default value |
| `onOpenChange` | `function` | No default value |
| `modal`        | `boolean`  | `true`           |

### [触发器](#触发器)

打开上下文菜单的区域。在右键单击（或使用相关的键盘快捷键）时，将其包裹在您希望上下文菜单打开的目标周围。

| Prop       | Type      | Default |
| ---------- | --------- | ------- |
| `asChild`  | `boolean` | `false` |
| `disabled` | `boolean` | `false` |

| Data attribute | Values               |
| -------------- | -------------------- |
| `[data-state]` | `"open" \| "closed"` |

### [门户](#门户)

使用时，将内容部分门户到 `body` 中。

| Prop         | Type          | Default          |
| ------------ | ------------- | ---------------- |
| `forceMount` | `boolean`     | No default value |
| `container`  | `HTMLElement` | `document.body`  |

### [内容](#内容)

在打开的上下文菜单中弹出的组件。

| Prop                   | Type                | Default          |
| ---------------------- | ------------------- | ---------------- |
| `asChild`              | `boolean`           | `false`          |
| `loop`                 | `boolean`           | `false`          |
| `onCloseAutoFocus`     | `function`          | No default value |
| `onEscapeKeyDown`      | `function`          | No default value |
| `onPointerDownOutside` | `function`          | No default value |
| `onFocusOutside`       | `function`          | No default value |
| `onInteractOutside`    | `function`          | No default value |
| `forceMount`           | `boolean`           | No default value |
| `alignOffset`          | `number`            | `0`              |
| `avoidCollisions`      | `boolean`           | `true`           |
| `collisionBoundary`    | `Boundary`          | `[]`             |
| `collisionPadding`     | `number \| Padding` | `0`              |
| `sticky`               | `enum`              | `"partial"`      |
| `hideWhenDetached`     | `boolean`           | `false`          |

| Data attribute | Values                                   |
| -------------- | ---------------------------------------- |
| `[data-state]` | `"open" \| "closed"`                     |
| `[data-side]`  | `"left" \| "right" \| "bottom" \| "top"` |
| `[data-align]` | `"start" \| "end" \| "center"`           |

| CSS Variable                                    | Description                                    |
| ----------------------------------------------- | ---------------------------------------------- |
| `--radix-context-menu-content-transform-origin` | 从内容和箭头位置/偏移计算的 `transform-origin` |
| `--radix-context-menu-content-available-width`  | 触发器和边界边缘之间的剩余宽度                 |
| `--radix-context-menu-content-available-height` | 触发器和边界边缘之间的剩余高度                 |
| `--radix-context-menu-trigger-width`            | 触发器的宽度                                   |
| `--radix-context-menu-trigger-height`           | 触发器的高度                                   |

### [箭头](#箭头)

可选的箭头元素，用于在子菜单旁边渲染。它可以用于帮助在视觉上将触发项目与 `ContextMenu.Content` 连接。必须在 `ContextMenu.Content` 内部渲染。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |
| `width`   | `number`  | `10`    |
| `height`  | `number`  | `5`     |

### [项目](#项目)

包含上下文菜单项的组件。

| Prop        | Type       | Default          |
| ----------- | ---------- | ---------------- |
| `asChild`   | `boolean`  | `false`          |
| `disabled`  | `boolean`  | No default value |
| `onSelect`  | `function` | No default value |
| `textValue` | `string`   | No default value |

| Data attribute       | Values       |
| -------------------- | ------------ |
| `[data-highlighted]` | 当高亮时存在 |
| `[data-disabled]`    | 当禁用时存在 |

### [组](#组)

用于将多个 `ContextMenu.Item` 分组。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [标签](#标签)

用于渲染标签。它不能使用箭头键获得焦点。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [复选框项目](#复选框项目)

可以像复选框一样控制和渲染的项目。

| Prop              | Type                         | Default          |
| ----------------- | ---------------------------- | ---------------- |
| `asChild`         | `boolean`                    | `false`          |
| `checked`         | `boolean \| 'indeterminate'` | No default value |
| `onCheckedChange` | `function`                   | No default value |
| `disabled`        | `boolean`                    | No default value |
| `onSelect`        | `function`                   | No default value |
| `textValue`       | `string`                     | No default value |

| Data attribute       | Values                                        |
| -------------------- | --------------------------------------------- |
| `[data-state]`       | `"checked" \| "unchecked" \| "indeterminate"` |
| `[data-highlighted]` | 当高亮时存在                                  |
| `[data-disabled]`    | 当禁用时存在                                  |

### [单选组](#单选组)

用于将多个 `ContextMenu.RadioItem` 分组。

| Prop            | Type       | Default          |
| --------------- | ---------- | ---------------- |
| `asChild`       | `boolean`  | `false`          |
| `value`         | `string`   | No default value |
| `onValueChange` | `function` | No default value |

### [单选项](#单选项)

可以像单选框一样控制和渲染的项目。

| Prop        | Type       | Default          |
| ----------- | ---------- | ---------------- |
| `asChild`   | `boolean`  | `false`          |
| `value*`    | `string`   | No default value |
| `disabled`  | `boolean`  | No default value |
| `onSelect`  | `function` | No default value |
| `textValue` | `string`   | No default value |

| Data attribute       | Values                                        |
| -------------------- | --------------------------------------------- |
| `[data-state]`       | `"checked" \| "unchecked" \| "indeterminate"` |
| `[data-highlighted]` | 当高亮时存在                                  |
| `[data-disabled]`    | 当禁用时存在                                  |

### [项目指示器](#项目指示器)

当父级 `ContextMenu.CheckboxItem` 或 `ContextMenu.RadioItem` 被选中时渲染。您可以直接为此元素设置样式，或者将其用作放置图标的包装器，或者两者兼有。

| Prop         | Type      | Default          |
| ------------ | --------- | ---------------- |
| `asChild`    | `boolean` | `false`          |
| `forceMount` | `boolean` | No default value |

| Data attribute | Values                                        |
| -------------- | --------------------------------------------- |
| `[data-state]` | `"checked" \| "unchecked" \| "indeterminate"` |

### [分隔符](#分隔符)

用于在上下文菜单中视觉上分隔项目。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [子](#子)

包含子菜单的所有部分。

| Prop           | Type       | Default          |
| -------------- | ---------- | ---------------- |
| `defaultOpen`  | `boolean`  | No default value |
| `open`         | `boolean`  | No default value |
| `onOpenChange` | `function` | No default value |

### [子触发器](#子触发器)

打开子菜单的项目。必须在 `ContextMenu.Sub` 内部渲染。

| Prop        | Type      | Default          |
| ----------- | --------- | ---------------- |
| `asChild`   | `boolean` | `false`          |
| `disabled`  | `boolean` | No default value |
| `textValue` | `string`  | No default value |

| Data attribute       | Values               |
| -------------------- | -------------------- |
| `[data-state]`       | `"open" \| "closed"` |
| `[data-highlighted]` | 当高亮时存在         |
| `[data-disabled]`    | 当禁用时存在         |

### [子内容](#子内容)

当子菜单打开时弹出的组件。必须在 `ContextMenu.Sub` 内部渲染。

| Prop                   | Type                | Default          |
| ---------------------- | ------------------- | ---------------- |
| `asChild`              | `boolean`           | `false`          |
| `loop`                 | `boolean`           | `false`          |
| `onEscapeKeyDown`      | `function`          | No default value |
| `onPointerDownOutside` | `function`          | No default value |
| `onFocusOutside`       | `function`          | No default value |
| `onInteractOutside`    | `function`          | No default value |
| `forceMount`           | `boolean`           | No default value |
| `sideOffset`           | `number`            | `0`              |
| `alignOffset`          | `number`            | `0`              |
| `avoidCollisions`      | `boolean`           | `true`           |
| `collisionBoundary`    | `Boundary`          | `[]`             |
| `collisionPadding`     | `number \| Padding` | `0`              |
| `arrowPadding`         | `number`            | `0`              |
| `sticky`               | `enum`              | `"partial"`      |
| `hideWhenDetached`     | `boolean`           | `false`          |

| Data attribute | Values                                   |
| -------------- | ---------------------------------------- |
| `[data-state]` | `"open" \| "closed"`                     |
| `[data-side]`  | `"left" \| "right" \| "bottom" \| "top"` |
| `[data-align]` | `"start" \| "end" \| "center"`           |

| CSS Variable                                    | Description                               |
| ----------------------------------------------- | ----------------------------------------- |
| `--radix-context-menu-content-transform-origin` | 从内容和箭头位置计算的 `transform-origin` |
| `--radix-context-menu-content-available-width`  | 触发器和边界边缘之间的剩余宽度            |
| `--radix-context-menu-content-available-height` | 触发器和边界边缘之间的剩余高度            |
| `--radix-context-menu-trigger-width`            | 触发器的宽度                              |
| `--radix-context-menu-trigger-height`           | 触发器的高度                              |

## [示例](#示例)

### [带子菜单](#带子菜单)

您可以通过结合使用 `ContextMenu.Sub` 和其部分来创建子菜单。

```jsx
<ContextMenu.Root>
  <ContextMenu.Trigger>…</ContextMenu.Trigger>

  <ContextMenu.Portal>
    <ContextMenu.Content>
      <ContextMenu.Item>…</ContextMenu.Item>

      <ContextMenu.Item>…</ContextMenu.Item>

      <ContextMenu.Separator />

      <ContextMenu.Sub>
        <ContextMenu.SubTrigger>子菜单 →</ContextMenu.SubTrigger>

        <ContextMenu.Portal>
          <ContextMenu.SubContent>
            <ContextMenu.Item>子菜单项目</ContextMenu.Item>

            <ContextMenu.Item>子菜单项目</ContextMenu.Item>

            <ContextMenu.Arrow />
          </ContextMenu.SubContent>
        </ContextMenu.Portal>
      </ContextMenu.Sub>

      <ContextMenu.Separator />

      <ContextMenu.Item>…</ContextMenu.Item>
    </ContextMenu.Content>
  </ContextMenu.Portal>
</ContextMenu.Root>
```

### [带禁用项目](#带禁用项目)

您可以通过 `data-disabled` 属性为禁用项目添加特殊样式。

```jsx
// index.jsx

import { ContextMenu } from "radix-ui";

import "./styles.css";

export default () => (
  <ContextMenu.Root>
    <ContextMenu.Trigger>…</ContextMenu.Trigger>

    <ContextMenu.Portal>
      <ContextMenu.Content>
        <ContextMenu.Item className="ContextMenuItem" disabled>
          …
        </ContextMenu.Item>

        <ContextMenu.Item className="ContextMenuItem">…</ContextMenu.Item>
      </ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu.Root>
);
```

```css
/* styles.css */

.ContextMenuItem[data-disabled] {
  color: gainsboro;
}
```

### [带分隔符](#带分隔符)

使用 `Separator` 部分在项目之间添加分隔符。

```jsx
<ContextMenu.Root>
  <ContextMenu.Trigger>…</ContextMenu.Trigger>

  <ContextMenu.Portal>
    <ContextMenu.Content>
      <ContextMenu.Item>…</ContextMenu.Item>

      <ContextMenu.Separator />

      <ContextMenu.Item>…</ContextMenu.Item>

      <ContextMenu.Separator />

      <ContextMenu.Item>…</ContextMenu.Item>
    </ContextMenu.Content>
  </ContextMenu.Portal>
</ContextMenu.Root>
```

### [带标签](#带标签)

使用 `Label` 部分帮助标记一个部分。

```jsx
<ContextMenu.Root>
  <ContextMenu.Trigger>…</ContextMenu.Trigger>

  <ContextMenu.Portal>
    <ContextMenu.Content>
      <ContextMenu.Label>标签</ContextMenu.Label>

      <ContextMenu.Item>…</ContextMenu.Item>

      <ContextMenu.Item>…</ContextMenu.Item>

      <ContextMenu.Item>…</ContextMenu.Item>
    </ContextMenu.Content>
  </ContextMenu.Portal>
</ContextMenu.Root>
```

### [带复选框项目](#带复选框项目)

使用 `CheckboxItem` 部分添加一个可被选中的项目。

```jsx
import * as React from "react";

import { CheckIcon } from "@radix-ui/react-icons";

import { ContextMenu } from "radix-ui";

export default () => {
  const [checked, setChecked] = React.useState(true);

  return (
    <ContextMenu.Root>
      <ContextMenu.Trigger>…</ContextMenu.Trigger>

      <ContextMenu.Portal>
        <ContextMenu.Content>
          <ContextMenu.Item>…</ContextMenu.Item>

          <ContextMenu.Item>…</ContextMenu.Item>

          <ContextMenu.Separator />

          <ContextMenu.CheckboxItem checked={checked} onCheckedChange={setChecked}>
            <ContextMenu.ItemIndicator>
              <CheckIcon />
            </ContextMenu.ItemIndicator>
            复选框项目
          </ContextMenu.CheckboxItem>
        </ContextMenu.Content>
      </ContextMenu.Portal>
    </ContextMenu.Root>
  );
};
```

### [带单选项目](#带单选项目)

使用 `RadioGroup` 和 `RadioItem` 部分添加一个可以在其他项目中选中的项目。

```jsx
import * as React from "react";

import { CheckIcon } from "@radix-ui/react-icons";

import { ContextMenu } from "radix-ui";

export default () => {
  const [color, setColor] = React.useState("blue");

  return (
    <ContextMenu.Root>
      <ContextMenu.Trigger>…</ContextMenu.Trigger>

      <ContextMenu.Portal>
        <ContextMenu.Content>
          <ContextMenu.RadioGroup value={color} onValueChange={setColor}>
            <ContextMenu.RadioItem value="red">
              <ContextMenu.ItemIndicator>
                <CheckIcon />
              </ContextMenu.ItemIndicator>
              红色
            </ContextMenu.RadioItem>

            <ContextMenu.RadioItem value="blue">
              <ContextMenu.ItemIndicator>
                <CheckIcon />
              </ContextMenu.ItemIndicator>
              蓝色
            </ContextMenu.RadioItem>

            <ContextMenu.RadioItem value="green">
              <ContextMenu.ItemIndicator>
                <CheckIcon />
              </ContextMenu.ItemIndicator>
              绿色
            </ContextMenu.RadioItem>
          </ContextMenu.RadioGroup>
        </ContextMenu.Content>
      </ContextMenu.Portal>
    </ContextMenu.Root>
  );
};
```

### [带复杂项目](#带复杂项目)

您可以在 `Item` 部分中添加额外的装饰元素，例如图像。

```jsx
import { ContextMenu } from "radix-ui";

export default () => (
  <ContextMenu.Root>
    <ContextMenu.Trigger>…</ContextMenu.Trigger>

    <ContextMenu.Portal>
      <ContextMenu.Content>
        <ContextMenu.Item>
          <img src="…" />
          阿道夫·赫斯
        </ContextMenu.Item>

        <ContextMenu.Item>
          <img src="…" />
          米亚·迈尔斯
        </ContextMenu.Item>
      </ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu.Root>
);
```

### [限制内容/子内容大小](#限制内容子内容大小)

您可能希望限制内容（或子内容）的宽度，以使其匹配触发器（或子触发器）的宽度。您也可以希望限制其高度，以不超过视口。

我们暴露了几个 CSS 自定义属性，例如 `--radix-context-menu-trigger-width` 和 `--radix-context-menu-content-available-height` 来支持这一点。使用它们来限制内容大小。

```jsx
// index.jsx

import { ContextMenu } from "radix-ui";

import "./styles.css";

export default () => (
  <ContextMenu.Root>
    <ContextMenu.Trigger>…</ContextMenu.Trigger>

    <ContextMenu.Portal>
      <ContextMenu.Content className="ContextMenuContent">…</ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu.Root>
);
```

```css
/* styles.css */

.ContextMenuContent {
  width: var(--radix-context-menu-trigger-width);

  max-height: var(--radix-context-menu-content-available-height);
}
```

### [基于位置的动画](#基于位置的动画)

我们暴露了一个 CSS 自定义属性 `--radix-context-menu-content-transform-origin`。使用它根据 `side`、`sideOffset`、`align`、`alignOffset` 和任何碰撞从其计算的原点动画内容。

```jsx
// index.jsx

import { ContextMenu } from "radix-ui";

import "./styles.css";

export default () => (
  <ContextMenu.Root>
    <ContextMenu.Trigger>…</ContextMenu.Trigger>

    <ContextMenu.Portal>
      <ContextMenu.Content className="ContextMenuContent">…</ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu.Root>
);
```

```css
/* styles.css */

.ContextMenuContent {
  transform-origin: var(--radix-context-menu-content-transform-origin);

  animation: scaleIn 0.5s ease-out;
}

@keyframes scaleIn {
  from {
    opacity: 0;

    transform: scale(0);
  }

  to {
    opacity: 1;

    transform: scale(1);
  }
}
```

### [基于碰撞的动画](#基于碰撞的动画)

我们暴露了 `data-side` 和 `data-align` 属性。它们的值在运行时将变化以反映碰撞。使用它们来创建基于碰撞和方向的动画。

```jsx
// index.jsx

import { ContextMenu } from "radix-ui";

import "./styles.css";

export default () => (
  <ContextMenu.Root>
    <ContextMenu.Trigger>…</ContextMenu.Trigger>

    <ContextMenu.Portal>
      <ContextMenu.Content className="ContextMenuContent">…</ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu.Root>
);
```

```css
/* styles.css */

.ContextMenuContent {
  animation-duration: 0.6s;

  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

.ContextMenuContent[data-side="top"] {
  animation-name: slideUp;
}

.ContextMenuContent[data-side="bottom"] {
  animation-name: slideDown;
}

@keyframes slideUp {
  from {
    opacity: 0;

    transform: translateY(10px);
  }

  to {
    opacity: 1;

    transform: translateY(0);
  }
}

@keyframes slideDown {
  from {
    opacity: 0;

    transform: translateY(-10px);
  }

  to {
    opacity: 1;

    transform: translateY(0);
  }
}
```

## [可访问性](#可访问性)

使用 [游走的 tabindex](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex) 来管理菜单项之间的焦点移动。

### [键盘交互](#键盘交互)

| Key                     | Description                                                            |
| ----------------------- | ---------------------------------------------------------------------- |
| `Space`                 | 激活焦点项。                                                           |
| `Enter`                 | 激活焦点项。                                                           |
| `ArrowDown`             | 将焦点移动到下一个项目。                                               |
| `ArrowUp`               | 将焦点移动到上一个项目。                                               |
| `ArrowRight``ArrowLeft` | 当焦点位于 `ContextMenu.SubTrigger` 时，根据阅读方向打开或关闭子菜单。 |
| `Esc`                   | 关闭上下文菜单                                                         |

<!--$-->

<!--/$-->
