컴포넌트
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
"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
/* ContextMenu: 공통 구조는 theme.css, 컴포넌트 전용 조정은 아래에 작성합니다. */
.rbx-context-trigger {
border: 1px dashed var(--rbx-color-stroke-emphasis);
border-radius: 8px;
padding: 32px;
text-align: center;
}