Switcher | CoUI

Switcher

여러 뷰 또는 콘텐츠 간 전환을 위한 스위처 컴포넌트

Switcher#

여러 뷰나 콘텐츠 섹션 간 전환을 위한 컴포넌트입니다. Tabs, Buttons, Pills 세 가지 변형을 지원합니다.

Live Preview#

Web
View A
Flutter
Loading Flutter...
Switcher([
  text('View A'),
  text('View B'),
  text('View C'),
], index: 0)
Switcher(
  index: 0,
  direction: AxisDirection.right,
  children: [
    Text('View A'),
    Text('View B'),
    Text('View C'),
  ],
)

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 목록/그리드처럼 동일 데이터의 뷰 모드를 전환할 때
  • 일/주/월처럼 필터 또는 기간 옵션을 선택할 때
  • 전체/진행중/완료 같은 상태 필터를 탭 형태로 표시할 때

대신 다른 컴포넌트를 사용하세요:

  • Tabs: 전환 시 별도 콘텐츠 패널이 표시되는 완전한 탭 UI에
  • Toggle: 두 가지 상태 중 하나를 선택하는 단순 On/Off에
  • Select: 선택지가 많아 드롭다운이 필요할 때

기본 사용법 (Basic Usage)#

// Tabs 변형 (기본)
Switcher(
  items: ['전체', '진행 중', '완료'],
  selectedIndex: _selectedIndex,
  onChanged: handleSwitcherChanged,
)

// Buttons 변형
Switcher(
  items: ['목록', '그리드', '카드'],
  selectedIndex: _selectedIndex,
  onChanged: handleSwitcherChanged,
  variant: SwitcherVariant.buttons,
)

// Pills 변형
Switcher(
  items: ['일', '주', '월', '년'],
  selectedIndex: _selectedIndex,
  onChanged: handleSwitcherChanged,
  variant: SwitcherVariant.pills,
)
// 수평 슬라이드 전환 (기본 — SwitcherDirection.horizontal)
Switcher(
  [
    div([text('첫 번째 뷰')]),
    div([text('두 번째 뷰')]),
    div([text('세 번째 뷰')]),
  ],
  index: currentIndex,
  direction: SwitcherDirection.horizontal,
  onIndexChanged: handleIndexChanged,
)

// 세로 슬라이드 전환
Switcher(
  [
    div([text('슬라이드 A')]),
    div([text('슬라이드 B')]),
  ],
  index: currentIndex,
  direction: SwitcherDirection.vertical,
  onIndexChanged: handleIndexChanged,
)

// 커스텀 전환 속도 (duration ms)
Switcher(
  [
    ProfileView(),
    SettingsView(),
  ],
  index: currentIndex,
  direction: SwitcherDirection.horizontal,
  duration: 500,
  onIndexChanged: handleIndexChanged,
)

Props / Parameters#

속성타입기본값설명
itemsList<String>필수전환 아이템 레이블 목록
selectedIndexint필수현재 선택된 아이템 인덱스
onChangedvoid Function(int)필수선택 변경 핸들러
variant SwitcherVariant tabs 스타일 변형

변형 (Variants)#

Tabs#

하단 인디케이터로 선택 상태를 표시합니다. 콘텐츠 영역 상단 탭 바에 적합합니다.

Switcher(
  items: ['개요', '세부사항', '리뷰'],
  selectedIndex: 0,
  onChanged: handleSwitcherChanged,
  variant: SwitcherVariant.tabs,
)

Buttons#

세그먼트 버튼 형태로 표시합니다. 뷰 모드 전환에 적합합니다.

Switcher(
  items: ['목록', '그리드'],
  selectedIndex: 0,
  onChanged: handleSwitcherChanged,
  variant: SwitcherVariant.buttons,
)

Pills#

알약 형태의 선택 인디케이터를 사용합니다. 필터나 기간 선택에 적합합니다.

Switcher(
  items: ['일', '주', '월'],
  selectedIndex: 0,
  onChanged: handleSwitcherChanged,
  variant: SwitcherVariant.pills,
)

동작 스펙 (Behavior)#

인터랙션#

  • 클릭/탭: 항목 클릭 시 onChanged(index) 호출
  • 호버: 항목 호버 시 배경색 미리보기 강조
  • 선택 항목: 클릭해도 이벤트 없음 (이미 선택된 상태)

상태 전환#

  • unselectedselected: 클릭 시 선택 인디케이터 이동
  • 선택 상태 전환 시 인디케이터 슬라이드 애니메이션

애니메이션#

  • 선택 인디케이터 이동: 200ms ease-in-out 슬라이드
  • pills 변형: 배경 캡슐 200ms 슬라이드

사용 가이드라인 (Usage Guidelines)#

✅ Do#

뷰 모드 전환에 Buttons variant 사용

Row(
  children: [
    CouiSwitcher(
      items: ['목록', '그리드'],
      selectedIndex: viewMode,
      onChanged: handleViewModeChanged,
      variant: SwitcherVariant.buttons,
    ),
    const Spacer(),
    Text('총 ${itemCount}개'),
  ],
)

Buttons variant는 뷰 모드 전환의 의미를 직관적으로 전달하며 세그먼트 컨트롤처럼 동작한다.


❌ Don't#

항목이 6개를 초과하는 경우 Switcher 사용하지 않기

// ❌ 너무 많은 항목
CouiSwitcher(
  items: ['월', '화', '수', '목', '금', '토', '일'],
  selectedIndex: 0,
  onChanged: handleDayChanged,
)

항목이 많으면 각 버튼이 너무 작아지고 레이블이 잘린다. Select나 Dropdown으로 대체한다.

✅ Do#

기간 필터에 pills variant 사용

CouiSwitcher(
  items: ['오늘', '이번 주', '이번 달', '올해'],
  selectedIndex: selectedPeriod,
  onChanged: handlePeriodChanged,
  variant: SwitcherVariant.pills,
)

Pills variant의 캡슐 형태가 필터 칩을 연상시켜 기간/카테고리 필터에 자연스럽다.


❌ Don't#

전환 시 페이지 이동이 필요한 경우 Switcher 사용

// ❌ 클릭 시 다른 라우트로 이동해야 하는 경우
CouiSwitcher(
  items: ['홈', '검색', '프로필'],
  selectedIndex: 0,
  onChanged: (index) {
    Navigator.pushNamed(context, routes[index]); // 페이지 이동
  },
)

라우트 이동이 필요한 경우 NavigationBarTabs가 더 적합하다.

✅ Do#

선택 상태를 명확하게 시각적으로 구분하세요.

CouiSwitcher(
  options: ['월간', '연간'],
  selectedIndex: billingCycle == BillingCycle.monthly ? 0 : 1,
  onChanged: handleBillingCycleChanged,
)

선택된 옵션과 미선택 옵션의 시각적 차이가 클수록 사용자가 현재 선택 상태를 명확히 인식할 수 있습니다.


❌ Don't#

Switcher에 3개 이상의 옵션을 넣으면 Tabs를 대신 사용하세요.

// ❌ 너무 많은 옵션을 Switcher로 구성
CouiSwitcher(
  options: ['일간', '주간', '월간', '분기', '연간'],  // 5개는 너무 많음
  selectedIndex: currentPeriod,
  onChanged: handlePeriodChanged,
)

Switcher는 2~3개 옵션에 최적화되어 있습니다. 옵션이 많아지면 Tabs나 SegmentedButton으로 전환하세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
TabSwitcher 그룹으로 포커스 이동
Arrow Left/Right이전/다음 항목 선택
Enter / Space포커스된 항목 선택

스크린 리더#

  • Flutter: role="tablist" + 각 항목 role="tab", aria-selected 자동 적용
  • Web: role="tablist" + role="tab" + aria-selected="true" 현재 선택 항목에 적용

터치 타겟#

  • 각 항목 최소 높이: 44dp

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명CouiSwitcherSwitcher
variant 타입 SwitcherVariant enum 문자열 ('tabs', 'buttons', 'pills')
인디케이터 AnimatedPositioned CSS transform: translateX()
배경 Container + BoxDecoration CSS background-color
  • Tabs: 전환 시 콘텐츠 패널이 함께 변경되는 완전한 탭 UI
  • Toggle: 두 가지 상태 중 하나를 선택하는 단순 토글

조합 예제#

// 뷰 모드 전환 + 콘텐츠 영역
Column(
  children: [
    Row(
      children: [
        CouiSwitcher(
          items: ['목록', '그리드'],
          selectedIndex: viewMode,
          onChanged: handleViewModeChanged,
          variant: SwitcherVariant.buttons,
        ),
        const Spacer(),
        Text('${items.length}개'),
      ],
    ),
    const Gap.md(),
    Expanded(
      child: viewMode == 0
          ? ListView.builder(
              itemCount: items.length,
              itemBuilder: (context, index) => ItemListTile(item: items[index]),
            )
          : GridView.builder(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
              itemCount: items.length,
              itemBuilder: (context, index) => ItemGridCard(item: items[index]),
            ),
    ),
  ],
)