컴포넌트/버튼

TabButton (탭버튼)

VanillaFront 2026. 9. 9. 16:51

Va.TabButton — 탭 UI를 위한 선택형 버튼

브라우저 탭이나 IDE 탭처럼 선택 상태 + 닫기 버튼 + 하단 인디케이터 바(bar) 를 갖춘 버튼입니다. 일반 Button/ToggleButton과 달리 부모 탭 컨테이너와 협력하는 라디오형 동작이 내장되어, 자신이 선택되면 형제 TabButton들을 자동으로 해제합니다.

  • 클래스: Va.TabButton  va_component.js:16599
  • short name: tabButton
  • 상속: Va.Component
  • isContainer: false
  • 기본 appearance: 'transparent' (탭 UI 관행에 맞춤)
  • 베이스 CSS: va-tab-button


1. 기본 사용

{
    tagName: 'tab',                     // 부모 탭 컨테이너 (.va-tab 클래스 필요)
    tags: [{
        tagName: 'tabButton',
        text: '홈',
        icon: 'ico_home',
        selected: true                  // 초기 선택
    }, {
        tagName: 'tabButton',
        text: '설정',
        icon: 'ico_settings',
        closable: true,                 // X 버튼 표시
        onClose: 'onCloseTab'
    }, {
        tagName: 'tabButton',
        text: '도움말'
    }]
}

핵심 자동 동작: 사용자가 하나의 TabButton을 클릭하면, 클릭 이벤트 핸들러가 parentComponent('.va-tab')을 찾아 형제 컴포넌트 모두에 unselect()를 호출한 뒤 자신을 select()합니다 (va_component.js:16652-16669).


2. 다른 버튼과의 차이

항목Va.ButtonVa.ToggleButtonVa.TabButton

상태 유지 ✓ (pressed) ✓ (selected)
그룹 라디오 동작 ✕ (수동 그룹핑) ✓ (부모 .va-tab이 있으면 자동)
닫기 버튼 ✓ (closable)
하단 인디케이터 바 ✓ (bar element)
위치 스타일 (top/right/bottom/left) ✓ (headerPosition)
ARIA 역할 없음 없음 role="tab", aria-selected
기본 appearance default default transparent

한 줄 요약: TabButton은 "탭 컨테이너 안에서 서로 배타적으로 선택되고, 닫힐 수 있는 버튼".


3. 주요 속성

탭 상태

속성기본값설명

selected false 현재 선택된 탭 여부. role="tab", aria-selected 자동 반영
closable true 닫기 X 버튼 표시 여부
noBar false 하단 인디케이터 바 숨김 (opacity 0)
headerPosition 'top' 탭 헤더 위치 (top/right/bottom/left) — 바 위치와 스타일에 영향

텍스트/아이콘 (Button 공통)

속성설명

text / innerHTML 탭 라벨
icon, iconPosition, iconOnly 아이콘
textAlign 텍스트 정렬
textellipsis 폭 초과 시 말줄임(...) 처리
appearance, shape, size 시각 스타일
disabled 비활성화
badge 배지 부착
stopPropagation 이벤트 버블링 차단 (기본 true)

textellipsis: true는 탭 목록에서 폭이 좁아질 때 라벨을 잘라주는 흔한 옵션입니다.


4. 이벤트

이벤트발생 시점

click 탭 클릭 (선택 상태 변경 이후 dispatch)
close X 버튼 클릭 — 탭 전용 이벤트. 부모가 실제 제거 처리
contextmenu 우클릭 (preventDefault 자동)
focus / blur 포커스 진입/이탈

close 이벤트 처리 패턴

onCloseTab(component, element, evt) {
    // TabButton 자체는 사라지지 않음 — 부모가 삭제해야 함
    const tab = this.getRef('mainTab');
    tab.removeChild(component);
}

TabButton은 X 클릭 시 이벤트만 dispatch하고 실제 제거는 부모 컨테이너(Tab)가 담당합니다 — Va.Tab.removeChild()는 헤더와 콘텐츠를 함께 정리하도록 오버라이드되어 있음 (CLAUDE.md 3장 컨벤션 참조).


5. 메서드

메서드설명

select() 선택 상태로 전환
unselect() 선택 해제
setText(text) / setInnerHTML(html) 라벨 변경
setIcon(icon) 아이콘 변경
setAppearance(v) / setShape(v) / setSize(v) 시각 속성 변경
setDisabled(bool) 비활성화
focus() selected=true로 설정 + 포커스
blur() selected=false로 설정 + 블러
setPressed(pressed) (레거시 인터페이스, 실제로 쓸 일 거의 없음 — 주석에 "확인필요" 표기)

⚠️ focus()/blur()의 부작용 주의: 일반 Button과 달리 TabButton의 focus()는 selected=true를 강제하고, blur()는 selected=false를 강제합니다. 단순히 포커스만 이동시키려는 의도로 호출하면 예기치 않게 탭 선택 상태가 바뀔 수 있습니다.


6. 내부 구조

<div elname="element" class="va-tab-button [top|right|bottom|left] [selected] [disabled]"
     role="tab" aria-selected="true|false">
  <span elname="inner" class="tab-button-inner">
    <button elname="button" class="va-button transparent" tabindex="0">
      <span elname="buttonInner" class="button-inner">
        <span elname="icon" class="icon ico_xxx [left|right|top|bottom]">…</span>  ← icon 지정 시
        <span elname="text">라벨</span>
        <span elname="close" class="icon close xsmall ico_dismiss"
              aria-label="탭 닫기">✕</span>                                        ← closable 시
      </span>
    </button>
    <span elname="bar" class="bar" style="opacity: 1|0"></span>                    ← 선택 인디케이터
  </span>
</div>

시각적 정체성은 bar element입니다 — 선택된 탭 아래(또는 headerPosition에 따라 옆)에 강조 선이 나타나는 흔한 탭 UI 패턴을 이 element로 구현합니다. noBar:true로 완전히 숨길 수 있습니다.


7. 라디오형 자동 선택 로직

가장 중요한 동작은 클릭 시 실행되는 형제 해제 로직입니다 (va_component.js:16656-16667):

let parentComponent = this.getParentComponent('.va-tab');
if (parentComponent != null) {
    let parentNode = this.element.parentNode;
    let childComponents = parentComponent.getChildComponentsInElement(parentNode);
    for (let i = 0; i < childComponents.length; i++) {
        if (typeof childComponents[i].select === 'function') {
            childComponents[i].unselect();
        }
    }
}
this.select();

의미:

  • 부모 트리에 .va-tab 클래스를 가진 컴포넌트가 있으면, 같은 부모 노드의 자식들 중 select 메서드를 가진 모든 컴포넌트를 unselect 한 뒤 자신을 select.
  • 부모가 .va-tab이 아니면(예: 그냥 div 안에 넣으면) 자동 배타 선택이 동작하지 않습니다 — 그때는 하나의 ToggleButton처럼만 동작.
  • select 메서드가 있는 형제라면 TabButton이 아니어도 unselect가 호출되므로, 커스텀 컴포넌트를 섞을 때 주의.

8. headerPosition이 하는 일

top/right/bottom/left 중 하나를 element 클래스로 추가합니다 (va_component.js:16710). CSS 측에서 이 클래스로 bar의 위치를 결정합니다 — top이면 하단 바, left면 우측 바 같은 식. 탭 그룹의 위치와 시각적으로 일치시키기 위한 속성.

부모 Tab 컨테이너가 이 값을 각 TabButton에 전달하는 게 일반적입니다.


9. 언제 쓰나

TabButton이 맞을 때

  • 브라우저 탭·IDE 탭 같은 닫을 수 있는 탭 UI
  • 콘텐츠 전환용 탭 헤더 (Tab 컨테이너와 함께)
  • 하단(또는 사이드) 인디케이터 바가 있는 선택 UI
  • 상호 배타적 선택이 필요한 목록

다른 걸 쓸 때

  • 닫기 없는 심플한 탭 → Va.SimpleTabButton
  • 사다리꼴 스타일 탭 (브라우저 탭 모양) → Va.TrapezoidButton (바로 다음에 정의된 형제)
  • 상태만 유지하는 단독 버튼 → Va.ToggleButton
  • 아코디언(펼침) → Va.AccordionButton
  • 팝업 메뉴 → Va.MenuButton

10. 알아두면 좋을 주의사항

  1. 부모가 .va-tab이어야 자동 배타 선택 — 그냥 div 안에 여러 TabButton을 넣으면 여러 개가 동시 선택될 수 있음. Tab 컨테이너와 세트로 쓰는 것이 정석.
  2. close 이벤트는 알림만 — TabButton은 자기 자신을 지우지 않으므로 부모가 removeChild로 실제 제거해야 함.
  3. closable이 기본 true — 닫기 아이콘이 원치 않는 위치에도 뜰 수 있으니 명시적으로 closable:false 지정 권장.
  4. focus()/blur()가 선택 상태를 건드림 — 단순 포커스 제어 목적으로 호출하지 말 것.
  5. Va.Tab.remove(childComponent) 잔재 — CLAUDE.md에 언급된 대로 옛 컨벤션 위반 코드가 va_tab.js:733에 남아 있음. 새 코드는 removeChild(child)로 수렴.

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

Link (링크)  (0) 2026.09.09
FileButton (파일버튼)  (0) 2026.09.09
ToggleButton (토글버튼)  (0) 2026.09.09
SplitButton (분할버튼)  (0) 2026.09.09
ResponsiveMenuButton (응답형메뉴버튼)  (0) 2026.09.09