Switcher#
여러 뷰나 콘텐츠 섹션 간 전환을 위한 컴포넌트입니다. Tabs, Buttons, Pills 세 가지 변형을 지원합니다.
Live Preview#
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#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
items | List<String> | 필수 | 전환 아이템 레이블 목록 |
selectedIndex | int | 필수 | 현재 선택된 아이템 인덱스 |
onChanged | void 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)호출 - 호버: 항목 호버 시 배경색 미리보기 강조
- 선택 항목: 클릭해도 이벤트 없음 (이미 선택된 상태)
상태 전환#
unselected→selected: 클릭 시 선택 인디케이터 이동- 선택 상태 전환 시 인디케이터 슬라이드 애니메이션
애니메이션#
- 선택 인디케이터 이동: 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]); // 페이지 이동
},
)
라우트 이동이 필요한 경우 NavigationBar나 Tabs가 더 적합하다.
✅ 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)#
키보드 인터랙션#
| 키 | 동작 |
|---|---|
Tab | Switcher 그룹으로 포커스 이동 |
Arrow Left/Right | 이전/다음 항목 선택 |
Enter / Space | 포커스된 항목 선택 |
스크린 리더#
-
Flutter:
role="tablist"+ 각 항목role="tab",aria-selected자동 적용 -
Web:
role="tablist"+role="tab"+aria-selected="true"현재 선택 항목에 적용
터치 타겟#
- 각 항목 최소 높이: 44dp
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | CouiSwitcher | Switcher |
| variant 타입 | SwitcherVariant enum |
문자열 ('tabs', 'buttons', 'pills') |
| 인디케이터 | AnimatedPositioned |
CSS transform: translateX() |
| 배경 | Container + BoxDecoration |
CSS background-color |
관련 컴포넌트 (Related Components)#
조합 예제#
// 뷰 모드 전환 + 콘텐츠 영역
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]),
),
),
],
)