컴포넌트/버튼

ResponsiveMenuButton (응답형메뉴버튼)

VanillaFront 2026. 9. 9. 15:24

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).

모바일 모드일 때

  1. 내부 MenuButton을 폭 100%로 늘림
  2. 자식 컴포넌트를 expandElement(인라인 영역)로 이동
  3. 모든 하위 menuItem/responsiveMenuItem의 mobileMode = true 설정
    • popElement.position = 'static' (플로팅 해제)
    • popDivElement.width = '100%'
  4. expanded 상태 토글하고 인라인 확장/축소

데스크톱/태블릿 모드일 때

  1. 자식 컴포넌트를 MenuButton의 popInnerElement(팝업 컨테이너)로 이동
  2. 모든 하위 항목의 mobileMode = false
    • popElement.position = 'absolute'
  3. popElement를 getHiddenAreaElement()로 이동 (부모 stacking context 회피)
  4. Va.addAutoHide(this.popElement)로 외부 클릭 자동 닫힘 등록
  5. 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. 알아두면 좋을 주의사항

  1. 자식은 responsiveMenuItem 사용 권장 — 코드가 이 타입일 때 모바일 전환 로직을 완벽히 적용. 일반 menuItem은 부분적으로만 대응.
  2. Va.mediaState 판정에 의존 — 부모 컨테이너 폭이 아니라 뷰포트 기준. 뷰포트가 데스크톱인데 좁은 컨테이너 안에 넣으면 팝업이 튀어나올 수 있음.
  3. 오토하이드는 데스크톱만 — 모바일에서는 버튼을 다시 눌러야 접힘. 자동 닫힘을 기대하지 말 것.
  4. 리사이즈로 모드 넘나들면 열려 있던 상태는 강제 닫힘 — 사용자가 보고 있던 상태가 리셋되므로, 리사이즈 중에 갑자기 사라지는 UX가 생길 수 있음.

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

ToggleButton (토글버튼)  (0) 2026.09.09
SplitButton (분할버튼)  (0) 2026.09.09
ExpandButton (확장버튼)  (0) 2026.09.09
MenuButton (메뉴버튼)  (0) 2026.09.09
CompoundButton (복합버튼)  (0) 2026.09.09