MENU
Wwoon

Floating

ContextMenu

우클릭으로 커서 위치에 열리는 컨텍스트 메뉴. 화살표키 내비게이션, typeahead, ARIA menu 패턴을 지원합니다.

설치

npm install @woon-ui/context-menu
import { ContextMenu } from '@woon-ui/context-menu'
import '@woon-ui/context-menu/css'

개요

import { ContextMenu } from '@woon-ui/context-menu'

ContextMenu는 우클릭 시 커서 위치에 열리는 컨텍스트 메뉴입니다. @floating-ui/react 기반으로 화면 경계를 자동 감지하며, 화살표키 내비게이션, typeahead, ARIA menu 패턴을 내장합니다.

기본 사용법

import { ContextMenu } from '@woon-ui/context-menu'

<ContextMenu.Root>
  <ContextMenu.Trigger asChild>
    <div>우클릭 하세요</div>
  </ContextMenu.Trigger>
  <ContextMenu.Content>
    <ContextMenu.Label>편집</ContextMenu.Label>
    <ContextMenu.Item onSelect={() => {}}>복사</ContextMenu.Item>
    <ContextMenu.Item onSelect={() => {}}>붙여넣기</ContextMenu.Item>
    <ContextMenu.Separator />
    <ContextMenu.Item onSelect={() => {}}>삭제</ContextMenu.Item>
  </ContextMenu.Content>
</ContextMenu.Root>

Preview

우클릭 하세요

컴파운드 컴포넌트

ContextMenu.Root

상태를 관리합니다. 포지셔닝 props는 없습니다 — 우클릭 커서 위치에 항상 열립니다.

PropTypeDefault
openboolean
제어 모드: 열림 상태
defaultOpenbooleanfalse
비제어 모드: 초기 열림 여부
onOpenChange(open: boolean) => void
열림 상태 변경 콜백

ContextMenu.Trigger

우클릭 이벤트를 캡처하는 래퍼입니다. asChild를 사용하면 자식 요소에 Trigger 동작을 위임합니다. 기본적으로 자체 div를 렌더합니다.

ContextMenu.Content

메뉴 컨테이너. role="menu"로 렌더되며 자식 Item들을 포함합니다.

ContextMenu.Item

PropTypeDefault
onSelect() => void
항목 선택 시 호출되는 콜백. 호출 후 메뉴가 자동으로 닫힙니다.
disabledbooleanfalse
비활성화. 클릭·키보드 선택이 막히고 시각적으로 흐리게 표시됩니다.
asChildbooleanfalse
true면 자식 요소에 Item 동작을 위임합니다.
textValuestring
typeahead에 사용할 텍스트. children이 문자열이면 자동 추출되므로 보통 불필요합니다.

ContextMenu.Label

항목 그룹의 비인터랙티브 레이블.

ContextMenu.Separator

항목 사이 구분선. role="separator"로 렌더됩니다.

ContextMenu.Group

관련 항목을 시맨틱으로 묶는 컨테이너. role="group"으로 렌더됩니다.

Disabled 항목

disabled prop을 사용하면 항목이 비활성화됩니다. 클릭과 키보드 선택이 막힙니다.

<ContextMenu.Item disabled>저장</ContextMenu.Item>

Preview

우클릭 하세요 (비활성 항목 포함)

Group과 Label

GroupLabel을 사용해 관련 항목을 묶고 레이블을 붙일 수 있습니다.

<ContextMenu.Group>
  <ContextMenu.Label>클립보드</ContextMenu.Label>
  <ContextMenu.Item onSelect={() => {}}>잘라내기</ContextMenu.Item>
  <ContextMenu.Item onSelect={() => {}}>복사</ContextMenu.Item>
</ContextMenu.Group>

Preview

우클릭 하세요 (그룹 메뉴)

스타일

@woon-ui/context-menu/css로 기본 스타일이 적용됩니다. 특정 값만 바꾸려면 선택자로 override하세요.

/* 하이라이트 색상 변경 */
[data-woon-context-menu-item][data-highlighted] {
  background-color: #eff6ff;
  color: #1d4ed8;
}

완전히 교체하려면 @woon-ui/react/css import를 제거하고 아래 CSS를 복사해서 수정하세요.

[data-woon-context-menu-floating] { z-index: 510; }

[data-woon-context-menu-content],
[data-woon-context-menu-content] *,
[data-woon-context-menu-content] *::before,
[data-woon-context-menu-content] *::after {
  box-sizing: border-box;
}

[data-woon-context-menu-content] {
  min-width: 8rem;
  padding: 0.25rem;
  background: #fff;
  border-radius: 8px;
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.06),
    0 4px 16px rgba(0, 0, 0, 0.12);
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 0.875rem;
  line-height: 1.5;
  outline: none;
}

[data-woon-context-menu-content][data-state='open'] {
  animation: woon-context-menu-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes woon-context-menu-in {
  from { opacity: 0; scale: 0.97; }
  to   { opacity: 1; scale: 1; }
}

[data-woon-context-menu-item] {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.625rem;
  border-radius: 5px;
  color: #18181b;
  cursor: default;
  user-select: none;
  outline: none;
  transition: background-color 100ms;
}

[data-woon-context-menu-item][data-highlighted] {
  background-color: #f4f4f5;
}

[data-woon-context-menu-item][data-disabled] {
  opacity: 0.4;
  pointer-events: none;
}

[data-woon-context-menu-separator] {
  height: 1px;
  margin: 0.25rem 0.625rem;
  background-color: #e4e4e7;
}

[data-woon-context-menu-label] {
  padding: 0.375rem 0.625rem 0.25rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: #71717a;
  user-select: none;
}

[data-woon-context-menu-group] + [data-woon-context-menu-group] {
  margin-top: 0.25rem;
}

@media (prefers-reduced-motion) {
  [data-woon-context-menu-content] { animation: none !important; }
}

접근성

  • Content에 role="menu" 적용
  • 각 Item에 role="menuitem", disabled 시 aria-disabled 적용
  • 화살표키 위/아래로 항목 간 이동
  • 첫 글자 입력으로 해당 항목으로 이동 (typeahead)
  • Enter / Space로 항목 선택
  • ESC로 닫기 (Dialog와 escape-stack 공유)
  • 외부 클릭으로 닫기
  • 닫힐 때 Trigger로 포커스 복귀

data 속성

속성대상
data-woon-context-menu-floating포지셔닝 래퍼
data-woon-context-menu-contentContent
data-stateContent"open"
data-sideContent"top" "right" "bottom" "left"
data-woon-context-menu-itemItem
data-highlightedItem현재 포커스된 항목
data-disabledItem비활성 항목
data-woon-context-menu-separatorSeparator
data-woon-context-menu-labelLabel
data-woon-context-menu-groupGroup