Components
Tabs
콘텐츠를 탭으로 전환하는 네비게이션 컴포넌트입니다.
Tiro는 두 가지 별개의 tab pattern을 사용. 서로 호환되지 않으며 용도가 다름:
- Underline tabs — 화면 내 top-level 콘텐츠 전환 (예: 노트 상세의 회의 메모 / 스크립트 / 1페이저)
- Segmented tabs — 폼/시트 내부의 mutually exclusive 토글 (예: 녹음 시트의 언어 설정 / 맥락 / Ask Tiro)
두 패턴 모두 mobile (RN) · desktop (Electron) · web 공유. 아래 스펙은 세 플랫폼 동일.
Mobile
기본 anatomy(underline · segmented)는 web과 동일하지만 모바일은 다음 동작·레이아웃이 추가된다.
Underline tabs (in-screen content switcher)
- 터치 타겟: 각 tab trigger 높이 ≥44pt. text-only tab이라도 vertical padding으로 hit area 확보
- Horizontal scroll: 탭 4개 초과 또는 라벨이 길어 한 화면에 가로로 안 들어가면
ScrollView horizontal로 가로 스크롤. 화면 좌우 끝 fade mask로 잘림 신호.showsHorizontalScrollIndicator={false} - 활성 탭 자동 스크롤: 선택된 탭이 화면 밖이면 자동으로 가시 영역 중앙으로 scroll-to
- Swipe-between-tabs (optional): 탭 콘텐츠 영역을 좌우 swipe로 전환 가능하게 —
react-native-pager-view. swipe 활성화 시 underline indicator가 손가락 위치에 따라 연속적으로 따라움직임 - 하단 Tab Bar와 혼동 금지: 화면 하단의 글로벌 네비 (Home/Search 등)는 별개 컴포넌트 — Navigation 참조. Tabs는 화면 내 콘텐츠 전환 전용
Segmented tabs (in form/sheet toggle)
- 레이아웃: 모바일에서는 컨테이너 너비를 꽉 채우고 각 세그먼트
flex-1로 동일 폭. 최대 3개까지 권장 (4개부터 underline tabs 또는 BottomSheet picker로 전환) - 터치 타겟: 세그먼트 높이 ≥44pt
- BottomSheet 내부 사용: Dialog mobile의 segmented header 패턴은 Dialog Mobile Presentation 참조
Preview
A. Underline Tabs
Top-level 콘텐츠 스위처. 화면 한 곳에 한 그룹만 존재.
Container & Items
| Token | Value | Description |
|---|---|---|
| container | inline-flex border-b border-stone-100 | 1px hairline track은 container에만. item에 절대 적용 금지 |
| item height (underline) | 44px | text 24px + py 10×2. iOS HIG 충족 |
| item padding | pt-2.5 pb-1 | 위 10px / 아래 4px |
| item gap | gap-2.5 (10px) | item 사이 간격 |
Text & States
| State | Color | Token |
|---|---|---|
| text (both states) | — | Pretendard JP 14/20 Medium (label1Medium) — 양 상태 동일 토큰 |
| active text | #674236 (brown-700) | |
| inactive text | #78716C (stone-400) | |
| hover (desktop) text | #57534E (stone-600) | indicator 변화 없음 |
| disabled text | #D6D3D1 (stone-300), opacity 50% |
Active Indicator
border-b-[2px] border-[#3A2018](brown-800), 2px- container의 1px track 위에 overlay — item 자체엔 border 없음
- 상태 차이는 색·indicator로만 표현. 글씨 굵기·크기 변화 금지
Optional Leading Slot
icon-only item이 첫 번째 child로 들어갈 수 있음 (예: refresh, settings). 같은 height 44px, p-2.5, border 없음, 텍스트 없음.
Reference markup
<div className="bg-white border-b border-stone-100 inline-flex">
{/* Optional leading icon slot */}
<div className="-mb-px px-2.5 pt-2.5 pb-1 flex justify-center items-center">
<Icon className="w-5 h-5" />
</div>
{/* Inactive item */}
<div className="-mb-px px-2.5 pt-2.5 pb-1 border-b-[2px] border-transparent flex justify-center items-center">
<span className="label1Medium text-stone-400">
대화기록
</span>
</div>
{/* Active item — overlays track with 2px brown indicator */}
<div className="-mb-px px-2.5 pt-2.5 pb-1 border-b-[2px] border-[#3A2018] flex justify-center items-center">
<span className="label1Medium text-brown-700">
스크립트
</span>
</div>
</div>Use cases
- Note Detail tab view (회의 메모 / 스크립트 / 1페이저 / 노트)
- Universal Search (Notes / Folders)
- Recording mode tabs (녹음하기 / 실시간 기록 / 음성파일)
B. Segmented Tabs
Form/sheet 내부 mutually exclusive 토글. 한 그룹 내 3–4 segment.
Container & Items
| Token | Value | Description |
|---|---|---|
| container | bg-stone-100 rounded-[10px] p-1 inline-flex gap-1.5 | 4px inner padding, 6px item gap |
| item | flex-1 h-7 | 28px, equal-width |
| item padding | px-2.5 | 10px (h-7 고정 높이라 py- 불필요) |
| active item radius | rounded-md (6px) | container 10px보다 4px 작음 |
Text & States
| State | Color | Token |
|---|---|---|
| text (both states) | — | Pretendard JP 14/20 Medium (label1Medium) — 양 상태 동일 토큰 |
| active background | #FFFFFF | surface |
| active text | #44403C (stone-700) | weight Medium (500) |
| inactive background | transparent | container track이 비침 |
| inactive text | #78716C (stone-500) | |
| disabled | opacity 50% |
Preview
Use cases
- 폴더 정보 / 자동화 설정 토글 (EditFolderPopup)
- 역할 안내 / 팀원 초대 / 팀원 내보내기 (TeamGuide)
- Recording sheet 언어 설정 / 맥락 / Ask Tiro 토글
- 폼 내부 mutually exclusive 옵션 (2–4 segments)
Spec
| Token | Underline | Segmented |
|---|---|---|
| typography | label1Medium 14/20 Medium | label1Medium 14/20 Medium |
| active text | brown-700 #674236 | stone-700 #44403C |
| inactive text | stone-400 #78716C | stone-500 #78716C |
| active indicator | border-b-[2px] brown-800 | bg-white rounded-md |
| track / container bg | stone-100 (1px border-b) | stone-100 rounded-[10px] |
| item height | 44px (Button lg) | 28px (Button xs) |
Active state 차별화는 색 / indicator / 배경으로만. 텍스트 크기·weight 변경 금지. 한 그룹 내 모든 segment는 동일 text token 사용.
A11y
role="tablist"+role="tab"+role="tabpanel"자동- Arrow 키 탭 이동, Home / End 처음 / 끝
- Active 탭만
tabindex={0}— roving focus - Segmented tabs:
role="radiogroup"+ 각 itemrole="radio"(mutually exclusive 의미 명시)
Do / Don't
- Do: 한 페이지 내 콘텐츠 필터링에 underline 사용
- Do: 폼/시트 mutually exclusive 옵션에 segmented 사용
- Do: 한 그룹 내 모든 segment는 동일 text token 공유
- Do: Active 상태는 색·indicator·배경으로만 차별화
- Don't: 두 패턴을 같은 화면에 동시 사용하면 위계 혼란
- Don't: URL이 바뀌는 네비게이션에 Tabs 사용 — 일반 링크 또는 stack navigation
- Don't: 하단 root tab bar 도입 — Tiro는 stack navigation 원칙
- Don't: 한 화면에 underline tab 그룹 2개 — 위계 모호
- Don't: Segmented tab 5개 초과 — 4 초과면 underline 또는 dropdown으로
- Don't: Active 상태에
font-bold(700) — SemiBold (600) 가 상한 - Do: 모든 Underline trigger에
-mb-px적용 — containerborder-b(1px)와 triggerborder-b-[2px](2px)가 정확히 겹쳐야 간격 없이 붙음 - Don't: Inactive underline item에
border-b추가 — track은 container에만
