컴포넌트

Context Menu

요소의 작업 메뉴를 우클릭으로 엽니다.

설치

pnpm dlx ply-ui add context-menu

공통 설치 설정을 먼저 적용합니다.

사용법

Trigger에 tabIndex를 넣어 키보드에서도 접근할 수 있게 합니다.

Shift+F10으로도 열 수 있습니다. 메뉴 항목의 onClick을 앱 동작에 연결합니다.

구성

구성 요소역할
Root메뉴의 열림 상태를 관리합니다.
Trigger컨텍스트 메뉴를 여는 대상 영역입니다.
Portal / Positioner메뉴를 별도 영역에 렌더하고 버튼을 기준으로 배치합니다.
Popup마우스 오른쪽 버튼이나 키보드로 열린 작업 목록의 표면입니다.
Item하나의 작업입니다. onClick으로 동작을 연결합니다.
Group / GroupLabel / Separator관련 작업을 묶거나 구분합니다.
SubmenuRoot / SubmenuTrigger하위 메뉴의 상태와 여는 항목을 구성합니다.
CheckboxItem / CheckboxItemIndicator서로 독립적인 켜짐·꺼짐 항목과 선택 표시입니다.
RadioGroup / RadioItem / RadioItemIndicator여러 값 중 하나를 선택하는 항목과 선택 표시입니다.

구현 코드

components/ui/context-menu.tsx
context-menu.tsx
"use client";

import type { ComponentProps } from "react";
import { ContextMenu as Primitive } from "@base-ui/react/context-menu";
import { withClassName } from "../../lib/cx";
import "./context-menu.css";

// 동작과 접근성은 Base UI가 담당합니다. 이 파일은 스타일 연결만 담당합니다.
function ContextMenuTrigger({
  className,
  ...props
}: ComponentProps<typeof Primitive.Trigger>) {
  return (
    <Primitive.Trigger
      {...props}
      className={withClassName("rbx-context-trigger", className)}
    />
  );
}

function ContextMenuPopup({
  className,
  ...props
}: ComponentProps<typeof Primitive.Popup>) {
  return (
    <Primitive.Popup
      {...props}
      className={withClassName("rbx-popup", className)}
    />
  );
}

function ContextMenuPositioner({
  className,
  ...props
}: ComponentProps<typeof Primitive.Positioner>) {
  return (
    <Primitive.Positioner
      {...props}
      className={withClassName("rbx-positioner", className)}
    />
  );
}

function ContextMenuItem({
  className,
  ...props
}: ComponentProps<typeof Primitive.Item>) {
  return (
    <Primitive.Item
      {...props}
      className={withClassName("rbx-option", className)}
    />
  );
}

function ContextMenuSubmenuTrigger({
  className,
  ...props
}: ComponentProps<typeof Primitive.SubmenuTrigger>) {
  return (
    <Primitive.SubmenuTrigger
      {...props}
      className={withClassName("rbx-option", className)}
    />
  );
}

function ContextMenuGroupLabel({
  className,
  ...props
}: ComponentProps<typeof Primitive.GroupLabel>) {
  return (
    <Primitive.GroupLabel
      {...props}
      className={withClassName("rbx-group-label", className)}
    />
  );
}

function ContextMenuCheckboxItem({
  className,
  ...props
}: ComponentProps<typeof Primitive.CheckboxItem>) {
  return (
    <Primitive.CheckboxItem
      {...props}
      className={withClassName("rbx-option", className)}
    />
  );
}

function ContextMenuCheckboxItemIndicator({
  className,
  ...props
}: ComponentProps<typeof Primitive.CheckboxItemIndicator>) {
  return (
    <Primitive.CheckboxItemIndicator
      {...props}
      className={withClassName("rbx-option-indicator", className)}
    />
  );
}

function ContextMenuRadioItem({
  className,
  ...props
}: ComponentProps<typeof Primitive.RadioItem>) {
  return (
    <Primitive.RadioItem
      {...props}
      className={withClassName("rbx-option", className)}
    />
  );
}

function ContextMenuRadioItemIndicator({
  className,
  ...props
}: ComponentProps<typeof Primitive.RadioItemIndicator>) {
  return (
    <Primitive.RadioItemIndicator
      {...props}
      className={withClassName("rbx-option-indicator", className)}
    />
  );
}

function ContextMenuSeparator({
  className,
  ...props
}: ComponentProps<typeof Primitive.Separator>) {
  return (
    <Primitive.Separator
      {...props}
      className={withClassName("rbx-separator", className)}
    />
  );
}

// Root/Portal 등 스타일 없는 파트와 제네릭 API는 원본을 그대로 보존합니다.
export const ContextMenu = {
  ...Primitive,
  Separator: ContextMenuSeparator,
  Trigger: ContextMenuTrigger,
  Popup: ContextMenuPopup,
  Positioner: ContextMenuPositioner,
  Item: ContextMenuItem,
  SubmenuTrigger: ContextMenuSubmenuTrigger,
  GroupLabel: ContextMenuGroupLabel,
  CheckboxItem: ContextMenuCheckboxItem,
  CheckboxItemIndicator: ContextMenuCheckboxItemIndicator,
  RadioItem: ContextMenuRadioItem,
  RadioItemIndicator: ContextMenuRadioItemIndicator,
};
components/ui/context-menu.css
context-menu.css
/* ContextMenu: 공통 구조는 theme.css, 컴포넌트 전용 조정은 아래에 작성합니다. */
.rbx-context-trigger {
  border: 1px dashed var(--rbx-color-stroke-emphasis);
  border-radius: 8px;
  padding: 32px;
  text-align: center;
}

목차