컴포넌트/버튼

MenuButton (메뉴버튼)

VanillaFront 2026. 9. 9. 15:14

Va.MenuButton — 드롭다운 팝업 메뉴 버튼

버튼을 클릭하면 아래로 팝업 메뉴가 펼쳐지고, 항목을 선택하면 접히는 컴포넌트입니다. 일반 Button과 달리 자식으로 menuItem을 담는 컨테이너이며, 팝업 위치·오토하이드·키보드 네비게이션까지 내장되어 있습니다.

  • 클래스: Va.MenuButton  va_component.js:13203
  • short name: menuButton
  • 상속: Va.Component (Button 상속 아님, 별도 구현)
  • isContainer: true — 자식 컴포넌트(menuItem)를 담을 수 있음
  • 베이스 CSS: va-menu-button, 팝업은 va-menu-pop


1. 기본 사용

자식 태그로 메뉴 정의

{
    tagName: 'menuButton',
    text: '메뉴 열기',
    tags: [
        { tagName: 'menuItem', key: '1', innerHTML: 'menu1' },
        { tagName: 'menuItem', key: '2', innerHTML: 'menu2' },
        { tagName: 'menuItem', seperator: true, size: 'xsmall' },  // 구분선
        { tagName: 'menuItem', key: '3', innerHTML: 'menu3' }
    ]
}

데이터 배열로 메뉴 정의 (template 방식)

{
    tagName: 'menuButton',
    text: '선택',
    template: {
        tagName: 'menuItem',
        layout: 'ds-flex fd-row ai-center',
        key: '{key}',
        innerHTML: '{display}'
    },
    data: [
        { key: '1', display: 'menu1', list: [        // ← 중첩 서브메뉴
            { key: '1-1', display: 'menu1-1' },
            { key: '1-2', display: 'menu1-2' }
        ]},
        { key: '2', display: 'menu2' },
        { key: '3', display: 'menu3' }
    ]
}

data 배열의 각 항목에 list를 넣으면 자동으로 계층형 서브메뉴가 생성됩니다.


2. Va.Button / CompoundButton과의 차이

항목Va.ButtonVa.CompoundButtonVa.MenuButton

역할 액션 트리거 정보성 액션 선택 팝업
컨테이너 여부 ✓ (자식 menuItem)
팝업 상태 expanded (true/false)
서브메뉴 지원 ✓ (data.list 재귀)
오토하이드 ✓ (Va.addAutoHide)
키보드 네비 기본 focus만 기본 focus만 Esc/Tab/Enter/↓↑ 처리
전용 이벤트 iconClick itemClick / select / expand / collapse / beforePop / afterPop / hidePop

MenuButton은 사실상 "버튼 + 팝업 컨테이너 + DataManager"를 통합한 미니 위젯입니다.


3. 주요 속성

버튼 표현 (Button과 공통 규약)

속성설명

text / innerHTML 버튼 라벨
icon, iconPosition, iconOnly, iconSize, iconRotate 좌측(또는 지정 위치) 아이콘
appearance, shape, size 시각 스타일 (Button 규약과 동일)
textAlign 텍스트 정렬
disabled 비활성화
badge 배지 부착
stopPropagation 이벤트 버블링 차단 (기본 true)
tabIndex 탭 순서 (기본 0)

메뉴 아이콘 (드롭다운 화살표)

속성기본값설명

menuIcon 'ico_chevron_down' 우측 화살표 아이콘
menuIconExpand 'ico_chevron_down' 확장 상태 아이콘
menuIconVisible true 화살표 표시 여부
menuIconSize 화살표 크기

iconOnly:true일 땐 자동으로 화살표가 숨겨집니다.

팝업 제어

속성기본값설명

expanded false 현재 펼침 상태
popWidth (버튼 폭) 팝업 너비. 미지정 시 버튼 폭과 동일
popMaxHeight 팝업 최대 높이. 넘치면 스크롤
autoHide true 외부 클릭 시 자동 닫힘
expandChildCheck true 자식이 없으면 팝업 안 열림
ignorePopPosition afterPop 후 재배치 무시 (외부에서 위치 제어할 때)

데이터

속성설명

data 배열. 자동으로 menuItem 자식 생성
template data 각 원소를 어떤 menuItem 구조로 렌더할지
fakeData 에디터(runtime='edit') 환경에서만 사용되는 미리보기 데이터

4. 이벤트

이벤트발생 시점

click 버튼 자체 클릭
contextmenu 우클릭
focus / blur 포커스 진입/이탈
itemClick 메뉴 항목 클릭
select 항목 선택 (itemClick과 함께 발생)
expand 팝업 열림
collapse 팝업 닫힘
beforePop 팝업 표시 직전
afterPop 팝업 표시 직후 (위치 재조정 훅)
hidePop 팝업 숨김 요청 시

itemClick / select 콜백 시그니처

onSelect(component, element, menuItem, data, isFolder, evt) {
    // menuItem: 클릭된 Va.MenuItem 인스턴스
    // data: 해당 항목의 데이터 객체
    // isFolder: 하위 list가 있는 폴더성 항목인지
}

5. 메서드

메서드설명

setText(text) / setInnerHTML(html) 라벨 변경
setIcon(icon) 아이콘 변경
setData(data) 메뉴 데이터 재바인딩 (기존 항목 replaceChildren)
expand() 팝업 열기 (200ms 지연)
collapse() 팝업 닫기 (200ms 지연)
showPop() / hidePop() 팝업 즉시 표시/숨김
focus() / blur() 포커스 제어
append(component) 자식 추가 (자동으로 component.menuButton = this 연결)

6. 내부 구조

<div elname="element" class="va-menu-button">
  <div elname="inner" class="menu-button-inner">
    <button elname="button" class="va-button" tabindex="0">
      <div elname="buttonInner" class="button-inner">
        <span elname="content" class="button-content">
          <span elname="icon" class="icon">…</span>          ← icon 지정 시
          <span elname="text" class="text">라벨</span>
        </span>
        <span elname="menuIcon" class="icon menu ico_chevron_down">…</span>
        <span cpname="badge">…</span>                        ← badge 지정 시
      </div>
    </button>
    <div elname="popDiv" class="menu-button-pop-div">        ← 원위치 컨테이너
      <div elname="pop" class="va-menu-pop">                 ← 열릴 때 hiddenArea로 이동
        <div elname="popInner" class="menu-pop-inner">
          <!-- menuItem들이 여기 렌더됨 (containerElement) -->
        </div>
      </div>
    </div>
  </div>
</div>

핵심 트릭: 팝업이 열릴 때 popElement를 getHiddenAreaElement()로 옮겨서 부모 컨테이너의 overflow:hidden, z-index 스택에 영향받지 않게 합니다 (va_component.js:13349). 닫히면 다시 원위치.


7. 팝업 위치 로직

showPop()이 매번 호출될 때 (va_component.js:13721-13748):

  1. 기본은 버튼 아래  top = 버튼.bottom + 2px
  2. 너비: popWidth 지정 없으면 버튼 폭과 동일 (min = max로 고정)
  3. 뷰포트 하단을 넘치면 위로 뒤집기  top = 버튼.top - 팝업.height - 3px
  4. IntersectionObserver로 팝업이 화면 밖으로 밀렸을 때 자동 재표시 (va_component.js:13305-13315)

8. 오토하이드 / 키보드

오토하이드 (프레임워크 팝업 상태머신 연동)

if(this.autoHide == null || this.autoHide == true){
    Va.addAutoHide(this.popElement);
}

전역 mousedown / resize / mousewheel / touchmove 이벤트가 발생하면 팝업이 닫힙니다. 프레임워크 공용 팝업 상태머신을 사용하므로, DatePicker/Combobox/Drawer 등 다른 팝업과 자연스럽게 상호 배타적으로 동작합니다.

⚠️ 이 상태머신은 CLAUDE.md 6장에서 "민감 영역"으로 지정되어 있습니다 — 여러 컴포넌트가 공유하므로 커스터마이징 시 주의.

키보드 조작 (va_component.js:13396-13426)

키동작

Enter 팝업 열기 + 첫 항목 포커스
 (ArrowDown) (Enter와 동일 분기로 흡수) 첫 항목 포커스
 (ArrowUp) 기본 스크롤 방지
Esc / Tab 팝업 닫고 버튼에 포커스 복귀

9. 서브메뉴 (재귀 데이터)

data의 각 원소가 list 필드를 가지면 자동으로 하위 Va.MenuItem이 만들어져 서브메뉴가 됩니다. Va.setMenuTemplate()이 재귀적으로 트리를 만들어 주고, expandChildCheck로 빈 폴더 방지, itemClickChild 이벤트로 하위 클릭을 상위로 버블링합니다.

data: [{
    key: 'file',
    display: 'File',
    list: [
        { key: 'open', display: 'Open' },
        { key: 'save', display: 'Save', list: [
            { key: 'save-as', display: 'Save As...' }
        ]}
    ]
}]

10. 언제 쓰나

MenuButton이 맞을 때

  • 액션 여러 개를 하나의 버튼으로 묶고 싶을 때 (More · 더보기 · Actions)
  • 계층형 선택지 (파일 메뉴, 카테고리 선택)
  • 커맨드 팔레트, 툴바 드롭다운

다른 걸 쓸 때

  • 값 선택이 목적 (검색·필터 포함) → Va.Combobox / Va.Filterbox
  • 단일 액션 → Va.Button
  • 우클릭 컨텍스트 메뉴 → Va.ContextMenu
  • 액션 실행 + 옵션 드롭다운을 분리하고 싶을 때 → Va.SplitButton

'컴포넌트 > 버튼' 카테고리의 다른 글

SplitButton (분할버튼)  (0) 2026.09.09
ResponsiveMenuButton (응답형메뉴버튼)  (0) 2026.09.09
ExpandButton (확장버튼)  (0) 2026.09.09
CompoundButton (복합버튼)  (0) 2026.09.09
Button (버튼)  (0) 2026.09.09