Menubar#
데스크탑 애플리케이션 스타일의 상단 가로 메뉴 바 컴포넌트입니다. 중첩 메뉴와 키보드 탐색을 지원합니다.
Live Preview#
Menubar([
MenuItem([text('File')]),
MenuItem([text('Edit')]),
MenuItem([text('View')]),
])
Card(
child: Row(
children: [
TextButton(onPressed: () {}, child: Text('File')),
TextButton(onPressed: () {}, child: Text('Edit')),
TextButton(onPressed: () {}, child: Text('View')),
],
),
)
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- IDE, 문서 편집기, 데스크탑 앱처럼 파일/편집/보기 스타일의 메뉴 바가 필요할 때
- 많은 기능을 계층적 메뉴 구조로 정리해야 할 때
- 키보드 단축키와 함께 메뉴 항목을 표시할 때
대신 다른 컴포넌트를 사용하세요:
Navigation: 페이지 간 이동을 위한 네비게이션 메뉴에DropdownMenu: 단일 버튼의 드롭다운 액션 목록에Tabs: 동일 화면 내 콘텐츠 섹션 전환에
기본 사용법 (Basic Usage)#
// 기본 메뉴 바
Menubar(
onSelect: handleMenubarSelect,
items: [
MenubarItem(
label: '파일',
menu: [
MenubarMenu(value: 'new', label: '새 파일'),
MenubarMenu(value: 'open', label: '열기'),
MenubarSeparator(),
MenubarMenu(value: 'save', label: '저장'),
MenubarMenu(value: 'saveAs', label: '다른 이름으로 저장'),
MenubarSeparator(),
MenubarMenu(value: 'exit', label: '종료'),
],
),
MenubarItem(
label: '편집',
menu: [
MenubarMenu(value: 'undo', label: '실행 취소'),
MenubarMenu(value: 'redo', label: '다시 실행'),
MenubarSeparator(),
MenubarMenu(value: 'cut', label: '잘라내기'),
MenubarMenu(value: 'copy', label: '복사'),
MenubarMenu(value: 'paste', label: '붙여넣기'),
],
),
MenubarItem(
label: '보기',
menu: [
MenubarMenu(value: 'zoomIn', label: '확대'),
MenubarMenu(value: 'zoomOut', label: '축소'),
MenubarMenu(value: 'resetZoom', label: '기본 배율'),
],
),
],
)
Menubar(
onSelect: handleMenubarSelect,
items: [
MenubarItem(
label: '파일',
menu: [
MenubarMenu(value: 'new', label: '새 파일'),
MenubarMenu(value: 'open', label: '열기'),
MenubarSeparator(),
MenubarMenu(value: 'save', label: '저장'),
],
),
MenubarItem(
label: '편집',
menu: [
MenubarMenu(value: 'undo', label: '실행 취소'),
MenubarMenu(value: 'redo', label: '다시 실행'),
],
),
],
)
Props / Parameters#
Menubar#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
items |
List<MenubarItem> |
필수 | 상단 메뉴 아이템 목록 |
onSelect |
void Function(String value)? |
null |
메뉴 아이템 선택 핸들러 |
dropdownMenuStyle |
CoreDropdownMenuStyle<Color, List<BoxShadow>>?
(Flutter) /
CoreDropdownMenuStyle<String, String>?
(Web)
|
null |
인스턴스 스타일 (Style 시스템 참조) |
스타일 시스템 (Style System)#
DropdownMenu / Menu / Menubar / ContextMenu 의 모든 chrome / dimensional / nested-slot 오버라이드는 공통
CoreDropdownMenuStyle<Clr, Shadow> 단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 4 종류 위젯이지만 chrome 은 모두 동일 Style 클래스로 받습니다.
시맨틱 vs 스타일#
- 시맨틱 / behaviour: 위젯 파라미터로 직접 (
border,popoverOffset…) -
chrome / dimensional / 슬롯 스타일:
CoreDropdownMenuStyle한 곳으로 (popoverStyle/triggerStyle/itemStyle/headerTextStyle+ flatseparatorColor/separatorThickness/separatorIndent) - 변형 교체: asChild — 트리거를 직접 위젯으로 주입
Resolve chain#
design system default for menubar
→ CoreDropdownMenuTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.dropdownMenuStyle // 인스턴스별
Menubar 의 dropdownMenuStyle 은 Data.maybeOf<MenubarState> 를 통해 자식 submenu 팝업으로도 자동 전파됩니다.
슬롯 매핑 (CoreDropdownMenuStyle 7 필드)#
| 필드 | 타입 (Flutter) | 적용 영역 |
|---|---|---|
popoverStyle |
CorePopoverStyle<Color, List<BoxShadow>>? |
메뉴바에서 열린 submenu 팝업 패널 chrome |
triggerStyle |
CoreButtonStyle<Color>? |
메뉴바 자체 (위젯이 자체 트리거를 렌더링할 때) |
itemStyle |
CoreButtonStyle<Color>? |
각 menubar 항목 / submenu 항목 chrome |
headerTextStyle |
CoreTextStyle<Color>? |
submenu 안 라벨 헤더 |
separatorColor |
Color? |
MenubarSeparator / MenubarDivider 색 |
separatorThickness | double? | divider 두께 |
separatorIndent | double? | divider 좌우 indent |
Migration — 옛 평면 필드 → 새 위치 매핑#
| 옛 chrome 필드 | 새 위치 |
|---|---|
MenubarTheme.backgroundColor |
dropdownMenuStyle.popoverStyle.panelBackgroundColor
(또는
Menubar.border
+ 기본 토큰)
|
MenubarTheme.borderColor |
dropdownMenuStyle.popoverStyle.panelBorderColor |
MenubarTheme.borderRadius |
dropdownMenuStyle.popoverStyle.panelBorderRadius |
MenubarTheme.padding |
dropdownMenuStyle.popoverStyle.panelPadding |
MenubarTheme.subMenuOffset | 위젯 popoverOffset 으로 유지 |
사용 예 (Flutter)#
Menubar(
dropdownMenuStyle: CoreDropdownMenuStyle(
popoverStyle: CorePopoverStyle(
panelBorderRadius: 8,
panelBackgroundColor: Color(0xFFFFFFFF),
),
),
children: [
// MenuButton(...)
],
)
사용 예 (Web)#
Menubar(
[
MenubarItem(label: 'File', children: [
MenubarAction(label: 'New', onSelect: () {}),
]),
],
dropdownMenuStyle: CoreDropdownMenuStyle<String, String>(
popoverStyle: CorePopoverStyle<String, String>(
panelBorderRadius: 8,
),
),
)
MenubarItem#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
label | String | 필수 | 메뉴 바에 표시되는 레이블 |
menu |
List<MenubarEntry> |
필수 | 클릭 시 표시할 드롭다운 메뉴 목록 |
MenubarMenu#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
value | String | 필수 | 선택 시 onSelect에 전달되는 값 |
label | String | 필수 | 메뉴 아이템 텍스트 |
shortcut |
String? |
null |
키보드 단축키 표시 (예: ⌘S) |
enabled | bool | true | 활성화 여부 |
MenubarSeparator#
메뉴 아이템 사이의 구분선입니다. 별도의 속성이 없습니다.
변형 (Variants)#
단축키 포함#
키보드 단축키를 함께 표시합니다.
Menubar(
onSelect: handleMenubarSelect,
items: [
MenubarItem(
label: '파일',
menu: [
MenubarMenu(value: 'new', label: '새 파일', shortcut: '⌘N'),
MenubarMenu(value: 'open', label: '열기', shortcut: '⌘O'),
MenubarMenu(value: 'save', label: '저장', shortcut: '⌘S'),
],
),
],
)
동작 스펙 (Behavior)#
인터랙션#
- 클릭: 메뉴 바 항목 클릭 시 드롭다운 열기
- 호버 이동: 한 메뉴가 열린 상태에서 다른 메뉴 바 항목 호버 시 자동 전환
- 항목 선택: 드롭다운 항목 클릭 시
onSelect(value)호출 후 닫힘 - 외부 클릭: 메뉴 외부 클릭 시 닫힘
상태 전환#
closed→open: 메뉴 바 항목 클릭open→open (다른 메뉴): 다른 메뉴 바 항목 호버open→closed: 항목 선택, 외부 클릭, Escape
애니메이션#
- 드롭다운 열림: 페이드 인 150ms
- 드롭다운 닫힘: 페이드 아웃 100ms
사용 가이드라인 (Usage Guidelines)#
✅ Do#
메뉴 항목을 논리적 그룹으로 구분선으로 분리
MenubarItem(
label: '파일',
menu: [
MenubarMenu(value: 'new', label: '새 파일', shortcut: '⌘N'),
MenubarMenu(value: 'open', label: '열기', shortcut: '⌘O'),
MenubarSeparator(), // 파일 작업과 저장 그룹 분리
MenubarMenu(value: 'save', label: '저장', shortcut: '⌘S'),
MenubarMenu(value: 'saveAs', label: '다른 이름으로 저장', shortcut: '⌘⇧S'),
MenubarSeparator(), // 저장과 종료 분리
MenubarMenu(value: 'exit', label: '종료', shortcut: '⌘Q'),
],
)
구분선으로 관련 항목을 그룹화하면 메뉴 탐색이 더 쉽고 직관적이다.
❌ Don't#
하나의 메뉴에 너무 많은 항목 사용 (10개 초과)
// ❌ 너무 많은 항목
MenubarItem(
label: '도구',
menu: List.generate(15, (i) => MenubarMenu(
value: 'tool_$i',
label: '도구 ${i + 1}',
)),
)
항목이 너무 많으면 화면을 넘치거나 탐색이 어려워진다. 서브메뉴로 계층화하거나 항목을 줄인다.
✅ Do#
자주 사용하는 항목에 키보드 단축키 표시
MenubarMenu(value: 'save', label: '저장', shortcut: '⌘S')
MenubarMenu(value: 'undo', label: '실행 취소', shortcut: '⌘Z')
MenubarMenu(value: 'copy', label: '복사', shortcut: '⌘C')
단축키 표시가 파워 유저의 생산성을 높이고 메뉴를 통해 단축키를 학습하게 한다.
❌ Don't#
모바일 앱의 주요 네비게이션으로 Menubar 사용
// ❌ 모바일에 데스크탑 스타일 Menubar 강요
CouiMenubar(
items: [
MenubarItem(label: '홈', menu: []),
MenubarItem(label: '검색', menu: []),
MenubarItem(label: '프로필', menu: []),
],
onSelect: handleMenubarSelect,
)
Menubar는 데스크탑 환경에 최적화되어 있다. 모바일에서는 NavigationBar나 Drawer가 적합하다.
✅ Do#
메뉴바 항목은 논리적 그룹으로 구성하세요.
CouiMenubar(
items: [
MenubarItem(label: '파일', children: fileMenuItems),
MenubarItem(label: '편집', children: editMenuItems),
MenubarItem(label: '보기', children: viewMenuItems),
MenubarItem(label: '도움말', children: helpMenuItems),
],
)
메뉴바 항목을 기능 그룹으로 묶으면 사용자가 원하는 기능을 빠르게 찾을 수 있습니다.
❌ Don't#
메뉴바에 너무 많은 최상위 항목을 넣지 마세요.
// ❌ 15개의 최상위 메뉴바 항목
CouiMenubar(
items: fifteenTopLevelItems, // 너무 많아 화면을 넘침
)
최상위 메뉴바 항목은 7개 이하가 적절합니다. 너무 많으면 중요한 항목이 묻히거나 화면 밖으로 밀려납니다.
접근성 (Accessibility)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | 메뉴 바 항목 간 이동 |
Enter / Space | 포커스된 메뉴 바 항목 열기 |
Arrow Down | 메뉴 열기 + 첫 항목 포커스 |
Arrow Up/Down | 드롭다운 항목 간 이동 |
Arrow Left/Right | 메뉴 바 상위 항목 간 이동 |
Escape | 메뉴 닫기 |
스크린 리더#
-
Flutter:
role="menubar"+role="menu"+role="menuitem"자동 적용 -
Web:
role="menubar"메뉴바에,role="menu"드롭다운에,role="menuitem"항목에 자동 적용
터치 타겟#
- 메뉴 바 항목 최소 높이: 44dp
- 드롭다운 항목 최소 높이: 44dp
크로스 플랫폼 차이점 (Platform Differences)#
v3.0부터 기본 API (
enabled,onChanged등)가 통일되었습니다. 아래는 플랫폼 고유 차이점만 나열합니다.
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | CouiMenubar | Menubar |
| 위치 | 수동 배치 (AppBar 아래 등) | 화면 상단 고정 또는 컨테이너 내 |
| 단축키 감지 | HardwareKeyboard | KeyboardEvent |
| 호버 전환 | MouseRegion | CSS :hover |
관련 컴포넌트 (Related Components)#
- Menu: 사이드바 형태의 일반 메뉴
- ContextMenu: 우클릭으로 표시되는 상황별 메뉴
- Navigation: 페이지 이동을 위한 네비게이션 메뉴
조합 예제#
// 데스크탑 앱 레이아웃
Scaffold(
body: Column(
children: [
// 메뉴 바
CouiMenubar(
onSelect: handleMenuSelect,
items: [
MenubarItem(
label: '파일',
menu: [
MenubarMenu(value: 'new', label: '새 파일', shortcut: '⌘N'),
MenubarMenu(value: 'open', label: '열기', shortcut: '⌘O'),
const MenubarSeparator(),
MenubarMenu(value: 'save', label: '저장', shortcut: '⌘S'),
],
),
MenubarItem(
label: '편집',
menu: [
MenubarMenu(value: 'undo', label: '실행 취소', shortcut: '⌘Z'),
MenubarMenu(value: 'redo', label: '다시 실행', shortcut: '⌘⇧Z'),
],
),
],
),
// 메인 콘텐츠 영역
Expanded(child: mainContent),
],
),
)