ResponsiveMenuButton (응답형메뉴버튼)
Va.ResponsiveMenuButton — 화면 크기에 따라 팝업/인라인이 자동 전환되는 메뉴 버튼
데스크톱에서는 MenuButton처럼 플로팅 팝업으로 열리고, 모바일에서는 ExpandButton처럼 인라인으로 펼쳐지는 하이브리드 컴포넌트입니다. 화면 폭 하나에 대응하는 UI를 두 벌 만들 필요 없이, 이 컴포넌트 하나로 두 형태를 자동 전환합니다.
- 클래스: Va.ResponsiveMenuButton — va_component.js:14681
- short name: responsiveMenuButton
- 상속: Va.Component — 내부에 Va.MenuButton 인스턴스를 감싸는 래퍼(wrapper) 구조
- isContainer: true
- 베이스 CSS: va-responsive-menu-button
- 작성 이력: 2025-07-09 반응형 동작 수정 (va_component.js:14680)


1. 기본 사용
자식으로는 responsiveMenuItem을 쓰는 것이 권장됩니다 (menuItem도 동작하지만 모바일 모드 전환 로직이 responsiveMenuItem 기준으로 최적화됨).
{
tagName: 'responsiveMenuButton',
text: '메뉴',
tags: [
{
tagName: 'responsiveMenuItem',
key: '1',
icon: 'ico_emoji_fill',
innerHTML: 'menu1',
tags: [ // 서브메뉴 (재귀 중첩)
{ tagName: 'responsiveMenuItem', key: '1-1', innerHTML: 'menu1-1' },
{ tagName: 'responsiveMenuItem', key: '1-2', innerHTML: 'menu1-2' }
]
},
{ tagName: 'responsiveMenuItem', key: '2', innerHTML: 'menu2' },
{ tagName: 'responsiveMenuItem', key: '3', innerHTML: 'menu3' }
]
}
데스크톱: 클릭 시 아래로 팝업이 뜨고, 서브메뉴는 옆으로 펼쳐집니다. 모바일: 클릭 시 뒤 콘텐츠를 밀어내며 세로로 들여쓰기(paddingLeft: 20px)되어 인라인 확장됩니다.
2. 다른 버튼 계열과의 차이
항목MenuButtonExpandButtonResponsiveMenuButton
| 데스크톱 표시 | 팝업 (플로팅) | 인라인 | 팝업 |
| 모바일 표시 | 팝업 (동일) | 인라인 (동일) | 인라인 (자동 전환) |
| 미디어 쿼리 반응 | ✕ | ✕ | ✓ (Va.addMediaQueryEvent) |
| 자식 타입 | menuItem | 임의 | responsiveMenuItem (권장) |
| 자체 구현 | ✓ | ✓ | MenuButton을 내부에 감싼 래퍼 |
| 오토하이드 | ✓ (팝업 밖 클릭) | ✕ | 데스크톱에서만 ✓ |
3. 미디어 상태 분기 로직
Va.mediaState가 'mobile'인지 아닌지에 따라 완전히 다른 흐름을 탑니다. 클릭 이벤트 핸들러에서 매번 판정합니다 (va_component.js:14722-14828).
모바일 모드일 때
- 내부 MenuButton을 폭 100%로 늘림
- 자식 컴포넌트를 expandElement(인라인 영역)로 이동
- 모든 하위 menuItem/responsiveMenuItem의 mobileMode = true 설정
- popElement.position = 'static' (플로팅 해제)
- popDivElement.width = '100%'
- expanded 상태 토글하고 인라인 확장/축소
데스크톱/태블릿 모드일 때
- 자식 컴포넌트를 MenuButton의 popInnerElement(팝업 컨테이너)로 이동
- 모든 하위 항목의 mobileMode = false
- popElement.position = 'absolute'
- popElement를 getHiddenAreaElement()로 이동 (부모 stacking context 회피)
- Va.addAutoHide(this.popElement)로 외부 클릭 자동 닫힘 등록
- expanded 토글
핵심: 같은 자식 컴포넌트가 모드에 따라 다른 DOM 위치로 옮겨 다닙니다. 즉, 컴포넌트 인스턴스는 하나인데 표시 컨테이너만 바뀝니다.
4. 미디어 쿼리 이벤트 등록
mounted()에서 한 번만 등록됩니다 (va_component.js:14884-14889):
mounted(){
Va.addMediaQueryEvent(this, (me, type)=> {
this.popElement.style.display = 'none';
this.expandElement.style.display = 'none';
});
}
브라우저 창을 리사이즈해 모바일/데스크톱 경계를 넘으면, 열려 있던 상태를 무조건 닫습니다 — 다음 클릭에서 그때의 mediaState에 맞는 형태로 새로 그리기 위함입니다.
5. 속성
내부 MenuButton으로 옵션이 그대로 spread되기 때문에, MenuButton의 대부분 속성이 유효합니다.
속성설명
| text / innerHTML | 라벨 |
| icon, iconPosition, iconSize | 아이콘 |
| appearance, shape, size | 시각 스타일 |
| textAlign | 데스크톱 텍스트 정렬 |
| mobileTextAlign | 모바일 텍스트 정렬 (전용 속성) |
| disabled | 비활성화 |
| expanded | 확장 상태 |
내부적으로 autoHide:false로 강제됩니다 (va_component.js:14696) — MenuButton의 기본 오토하이드를 끄고, 데스크톱 모드일 때만 ResponsiveMenuButton 자신이 Va.addAutoHide(this.popElement)를 수동 호출해 제어합니다.
6. 이벤트
이벤트발생 시점
| click | 버튼 클릭 시 (모드 무관) |
| expand | 확장 시 |
| collapse | 축소 시 (내부 로직에서 expand와 collapse가 반대로 호출되는 코드 흐름이 있으니 실제 발생 순서는 확인 필요) |
| beforePop | 팝업/확장 표시 직전 |
| focus / blur | 내부 MenuButton에 위임 |
| contextmenu | 우클릭 (기본 컨텍스트 메뉴는 preventDefault로 억제) |
⚠️ 실제 코드(va_component.js:14800, 14825)를 보면 데스크톱 모드에서 열릴 때 collapse가, 닫힐 때 expand가 dispatch되는 것처럼 보이는 부분이 있습니다. 실사용에서 이벤트가 반대로 오면 이 코드를 의심할 것.
7. 메서드
ResponsiveMenuButton은 자체 메서드가 매우 적고, 대부분 내부 MenuButton에 위임합니다.
메서드설명
| setText(text) | 내부 MenuButton으로 위임 |
| setIcon(icon) | 내부 MenuButton으로 위임 |
| expand() | 200ms 지연 후 확장 |
| collapse() | 200ms 지연 후 축소 |
| update() | mediaState에 따라 popElement / expandElement의 display 재조정 |
주의: setAppearance, setShape, setSize, setDisabled, focus, blur 같은 메서드는 이 클래스에 직접 정의되어 있지 않습니다. Demo의 Method Test는 실제로는 menuButtonComponent가 처리하는 것에 의존하는 셈이라, 일부 동작이 기대와 다를 수 있습니다.
8. 내부 구조
<div elname="element" class="va-responsive-menu-button" style="display:flex; flex-direction:column">
<div cpname="menuButton" class="va-menu-button"> ← 내부 MenuButton 통째로
<div class="menu-button-inner">
<button class="va-button">…</button>
<div class="menu-button-pop-div">
<div elname="pop" class="va-menu-pop">…</div> ← 데스크톱에서 여기로 자식 이동
</div>
</div>
</div>
<div elname="expand" class="expand"> ← 모바일에서 여기로 자식 이동
<!-- 확장 시 style.paddingLeft = '20px', display: flex column -->
</div>
</div>
두 개의 컨테이너 슬롯을 미리 만들어 두고 (containerElements = [expandElement, popInnerElement]), 상황에 맞는 쪽으로 자식을 옮기는 것이 이 컴포넌트의 핵심 트릭입니다.
9. 언제 쓰나
ResponsiveMenuButton이 맞을 때
- 반응형 앱바 · 툴바에서 액션 그룹핑 (데스크톱은 팝업이 자연스럽지만 모바일에선 인라인이 낫다)
- 모바일 사이드바 메뉴 (탭 → 확장) + 데스크톱 헤더 메뉴 (탭 → 팝업)을 한 컴포넌트로 처리하고 싶을 때
- 계층형 카테고리 선택 UI를 반응형으로 제공할 때
다른 걸 쓸 때
- 데스크톱만 지원 / 모바일에서도 팝업이 낫다 → Va.MenuButton
- 항상 인라인이면 됨 → Va.ExpandButton
- 반응형이지만 메뉴가 아니라 액션 트리거 → Va.ResponsiveButton (같은 계열의 형제 컴포넌트)
- 좌측 네비게이션 전용 → Va.NavMenuButton
10. 알아두면 좋을 주의사항
- 자식은 responsiveMenuItem 사용 권장 — 코드가 이 타입일 때 모바일 전환 로직을 완벽히 적용. 일반 menuItem은 부분적으로만 대응.
- Va.mediaState 판정에 의존 — 부모 컨테이너 폭이 아니라 뷰포트 기준. 뷰포트가 데스크톱인데 좁은 컨테이너 안에 넣으면 팝업이 튀어나올 수 있음.
- 오토하이드는 데스크톱만 — 모바일에서는 버튼을 다시 눌러야 접힘. 자동 닫힘을 기대하지 말 것.
- 리사이즈로 모드 넘나들면 열려 있던 상태는 강제 닫힘 — 사용자가 보고 있던 상태가 리셋되므로, 리사이즈 중에 갑자기 사라지는 UX가 생길 수 있음.