ContextMenu | CoUI

ContextMenu

우클릭 또는 길게 누르기로 표시되는 컨텍스트 메뉴 컴포넌트

ContextMenu#

우클릭(데스크탑) 또는 길게 누르기(모바일 — Flutter)로 표시되는 상황별 메뉴 컴포넌트입니다. 자식 영역 안에서 우클릭하면 액션 목록이 포인터 위치에 떠 있는 패널로 나타납니다.

ContextMenu는 레거시 Flutter ContextMenu와 Web ContextMenu를 통일한 컴포넌트입니다. Flutter와 Web이 동일한 파라미터 이름·동작·시각을 갖습니다. 액션 항목은 통합 CoreMenuItem 모델을 씁니다 — 컨텍스트 메뉴와 드롭다운 메뉴는 동일한 액션 / 구분선 엔트리 의미를 공유합니다.

Live Preview#

Web
Right-click here
Flutter
Loading Flutter...
class ContextMenuDefaultExample extends StatelessComponent {
  const ContextMenuDefaultExample({super.key});

  @override
  Component build(BuildContext context) {
    return ContextMenu(
      items: [
        CoreMenuItem.action('Copy', onPressed: () {}),
        CoreMenuItem.action('Paste', onPressed: () {}),
        const CoreMenuItem.divider(),
        CoreMenuItem.action('Delete', onPressed: () {}),
      ],
      child: div(
        [Text('Right-click here').bodyMedium],
        classes: 'border rounded-${CoreRadius.scale.radius8} '
            'px-${CoreSpace.scale.space24} py-${CoreSpace.scale.space16}',
      ),
    );
  }
}
class ContextMenuDefaultExample extends StatelessWidget {
  const ContextMenuDefaultExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ContextMenu(
      items: [
        CoreMenuItem.action('Copy', onPressed: () {}),
        CoreMenuItem.action('Paste', onPressed: () {}),
        const CoreMenuItem.divider(),
        CoreMenuItem.action('Delete', onPressed: () {}),
      ],
      child: DecoratedBox(
        decoration: BoxDecoration(
          border: Border.all(
            color: context.theme.colorScheme.outline!.toValue(),
            width: CoreStrokeWidth.stroke1,
          ),
          borderRadius: BorderRadius.circular(CoreRadius.radius8),
        ),
        child: Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: CoreSpace.space24,
            vertical: CoreSpace.space16,
          ),
          child: Text('Right-click here').bodyMedium,
        ),
      ),
    );
  }
}

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 우클릭으로 항목별 액션 메뉴를 제공할 때
  • 파일 / 행 / 카드 등에 상황별 액션이 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • DropdownMenu: 버튼 클릭으로 여는 드롭다운에
  • Menubar: 데스크탑 앱 상단 가로 메뉴 바에

기본 사용법 (Basic Usage)#

ContextMenu(
  items: [
    CoreMenuItem.action('Copy', onPressed: () {}),
    CoreMenuItem.action('Paste', onPressed: () {}),
    const CoreMenuItem.divider(),
    CoreMenuItem.action('Delete', onPressed: () {}),
  ],
  child: const Text('Right-click here'),
)
ContextMenu(
  items: [
    CoreMenuItem.action('Copy', onPressed: () {}),
    CoreMenuItem.action('Paste', onPressed: () {}),
    const CoreMenuItem.divider(),
    CoreMenuItem.action('Delete', onPressed: () {}),
  ],
  child: div([text('Right-click here')]),
)

Props / Parameters#

ContextMenu#

속성타입기본값설명
childW필수우클릭 트리거 영역
items List<CoreMenuItem<W>> 필수 컨텍스트 메뉴 패널의 행 — Menu / DropdownMenu / Menubar 와 공유하는 통합 [CoreMenuItem] 모델
enabled bool true 컨텍스트 메뉴 활성화 여부
popupStyle CorePopupStyle? null 인스턴스별 패널 chrome 스타일

CoreMenuItem (컨텍스트 메뉴에서 쓰는 종류)#

컨텍스트 메뉴는 통합 CoreMenuItem 모델을 받는다. 주로 쓰는 두 종류:

생성자속성타입기본값설명
.action label (positional) String 필수 액션 행 텍스트
.action shortcut String? null 라벨 뒤에 표시되는 단축키 힌트
.action disabled bool false true 시 muted 처리 + onPressed 미호출
.action onPressed void Function()? null 활성 액션 선택 시 호출
.divider그룹 사이 구분선

스타일 시스템 (Style System)#

ContextMenu의 메뉴 패널은 떠 있는 popup 표면이므로 CorePopupStyle chrome 슬롯을 공유합니다 (Epic #1415 option D). 엔트리 행 chrome 은 CoreDropdownMenuStyledefaultEntryPadding / defaultEntryBorderRadius / defaultEntryHoverColor / defaultDividerColor / defaultEntryDuration 디자인 시스템 기본값으로 고정됩니다.

Resolve chain#

CorePopupStyle.defaultX                          // 디자인 시스템 기본값
  → CorePopupTheme.style                          // 프로젝트 공통
  → widget.popupStyle                             // 인스턴스별

동작 스펙 (Behavior)#

인터랙션#

  • 우클릭: 자식 영역 우클릭 시 포인터 위치에 메뉴 열림
  • 길게 누르기: 모바일(Flutter) 에서 길게 누르면 메뉴 열림
  • 항목 선택: 활성 항목 클릭 시 onPressed 호출 후 닫힘
  • 외부 클릭 / Escape: 메뉴 닫힘

애니메이션#

  • 엔트리 hover 배경: 150ms 색상 전환 (CoreDuration.ms150)

사용 가이드라인 (Usage Guidelines)#

✅ Do#

관련 액션을 구분선으로 그룹화

ContextMenu(
  items: const [
    CoreMenuItem.action('Cut'),
    CoreMenuItem.action('Copy'),
    CoreMenuItem.divider(),
    CoreMenuItem.action('Delete'),
  ],
  child: const Text('Item'),
)

❌ Don't#

유일한 진입점으로 컨텍스트 메뉴 사용

// ❌ 우클릭으로만 접근 가능한 핵심 액션
ContextMenu(
  items: const [CoreMenuItem.action('Save')],
  child: editor,
)

핵심 액션은 보이는 버튼으로도 제공해야 한다. 컨텍스트 메뉴는 보조 경로다.

접근성 (Accessibility)#

시맨틱 역할#

  • 메뉴 패널: role="menu"
  • 엔트리: role="menuitem" (비활성 시 aria-disabled)
  • 구분선: role="separator"

키보드 인터랙션#

동작
Escape열린 메뉴 닫기

크로스 플랫폼 차이점 (Platform Differences)#

ContextMenu는 Flutter와 Web이 동일한 child / items / enabled / popupStyle API와 동작을 갖습니다. 메뉴 패널은 Flutter에서는 OverlayPortal, Web에서는 루트 OverlayHost의 menu 레이어로 portal되어 양쪽 모두 ancestor overflow에 잘리지 않습니다. Web은 우클릭 (contextmenu)으로, Flutter는 우클릭 + 모바일 길게 누르기로 메뉴를 엽니다.

  • DropdownMenu: 버튼 클릭 드롭다운
  • Menu: 사이드바 형태의 일반 메뉴
  • Popup: 플로팅 패널 컨테이너