Components / Navigation
Bottom CTA
화면 하단에 고정되는 주요 CTA 버튼 컨테이너입니다. 단일/듀얼 버튼, 보조 텍스트, Safe Area 대응을 지원합니다.
사용법
BottomCTA는 화면 최하단에 고정(position: fixed)되어, 스크롤과 무관하게 항상 노출됩니다. iOS Safe Area를 자동으로 처리합니다.
<BottomCTA> <BottomCTA.Button>구매 문의하기</BottomCTA.Button> </BottomCTA>
<div style="position:fixed;bottom:0;left:0;right:0;background:var(--bg-card);border-top:1px solid var(--border);padding:var(--space-3) var(--space-5);display:flex;gap:var(--space-3);justify-content:center"> <button class="ch-btn ch-btn--ghost" style="flex:1;max-width:200px">취소</button> <button class="ch-btn ch-btn--primary" style="flex:1;max-width:200px">구매하기</button> </div>
단일 버튼
화면에서 가장 중요한 단일 액션을 유도합니다. 전체 너비로 표시됩니다.
<BottomCTA>
<BottomCTA.Button color="primary">
작품 구매하기
</BottomCTA.Button>
</BottomCTA>
듀얼 버튼
주요 액션 + 보조 액션을 나란히 배치합니다. 보조 버튼은 weak 스타일로 표현합니다.
<BottomCTA>
<BottomCTA.Sub variant="weak">찜하기</BottomCTA.Sub>
<BottomCTA.Button>구매 문의</BottomCTA.Button>
</BottomCTA>
{/* Sub는 flex:1, Button은 flex:2 비율 */}
보조 텍스트
버튼 위에 가격, 할인, 안내 텍스트를 표시합니다.
<BottomCTA>
<BottomCTA.Info
left="예상 가격"
right="₩ 2,400,000"
/>
<BottomCTA.Button>구매 문의하기</BottomCTA.Button>
</BottomCTA>
접근성
BottomCTA는 페이지의 주요 액션 영역으로, 키보드 및 스크린 리더 접근성을 보장합니다.
| 속성 | 값 | 설명 |
|---|---|---|
role | "toolbar" | CTA 영역이 액션 도구 모음임을 명시합니다. |
aria-label | "주요 액션" | 영역의 용도를 설명합니다. |
position: fixed | — | 포커스 이동 시 항상 접근 가능합니다. |
safe-area-inset | — | iOS 하단 Safe Area를 자동 처리합니다. |
사용 방법 예시
작품 상세 페이지에서 BottomCTA가 사용되는 실제 화면입니다.
빨래터
박수근 · 유화
인터페이스
BottomCTA 및 서브 컴포넌트의 Props를 정의합니다.
BottomCTAProps
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
children | ✓ | — | ReactNode | Button, Sub, Info 서브 컴포넌트를 조합합니다. |
blur | — | true | boolean | 배경 blur 효과 적용 여부입니다. |
safeArea | — | true | boolean | iOS Safe Area inset 대응 여부입니다. |
BottomCTA.Button
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
color | — | primary | primary | dark | 버튼 색상입니다. |
loading | — | false | boolean | 로딩 상태입니다. |
disabled | — | false | boolean | 비활성 상태입니다. |
onClick | — | — | () => void | 클릭 핸들러입니다. |
BottomCTA.Info
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
left | — | — | string | 왼쪽 라벨 텍스트입니다. |
right | — | — | string | 오른쪽 값 텍스트입니다. |
크기 (Size)
3단계 사이즈 스케일을 지원합니다. 기본값은 md입니다.
상태 (States)
BottomCTA 버튼의 인터랙션 상태입니다. Explorer 툴바에서 실시간으로 전환할 수 있습니다.
반응형 (Responsive)
브레이크포인트별 동작 변화입니다.
fixed bottom, 100% width
fixed, max-w: 480px
inline, within content flow
Components / Navigation
Navigation Bar
앱 하단에 고정되는 글로벌 내비게이션입니다. 최대 5개 탭을 아이콘+라벨로 표시하며, 알림 dot을 지원합니다.
사용법
Navigation은 앱의 최상위 라우팅을 담당합니다. 모든 화면에서 하단에 고정되며, 현재 위치를 색상으로 표시합니다.
<Navigation activeIndex={0}>
<Navigation.Item icon={<HomeIcon />} label="홈" route="/" />
<Navigation.Item icon={<SearchIcon />} label="검색" route="/search" />
<Navigation.Item icon={<HeartIcon />} label="찜" route="/wishlist" />
<Navigation.Item icon={<ProfileIcon />} label="마이" route="/my" />
</Navigation>
<nav>
<div class="ch-sidebar-nav__item is-active">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
Dashboard
</div>
<div class="ch-sidebar-nav__item">
Settings
<span class="ch-sidebar-nav__badge">3</span>
</div>
</nav>
탭 구성
CHAART. 앱의 네비게이션은 4~5개 탭으로 구성됩니다. 각 탭은 아이콘과 라벨을 갖습니다.
4-Tab (기본)
홈
검색
찜
마이
5-Tab (확장)
홈
검색
찜
채팅
마이
{/* maxItems는 최대 5개까지 지원 */}
<Navigation maxItems={5}>
...
</Navigation>
활성 상태 표현
활성 탭은 CHAART. Red 색상 + bold 텍스트로 표시됩니다. 비활성 탭은 text-muted 색상입니다.
Active
Inactive
{/* Active: icon filled + label bold + CHAART. Red */}
{/* Inactive: icon stroke + label regular + text-muted */}
알림 Dot (Red Bean)
새 알림이 있는 탭에 6px 빨간 dot을 표시합니다. 해당 탭 진입 시 자동으로 사라집니다.
<Navigation badge={{ 2: true }}>
{/* index 2 (찜) 탭에 알림 dot 표시 */}
</Navigation>
접근성
Navigation은 앱의 최상위 내비게이션으로, ARIA 랜드마크와 키보드 탐색을 지원합니다.
| 속성 | 값 | 설명 |
|---|---|---|
role | "navigation" | 내비게이션 랜드마크 역할입니다. |
aria-label | "메인 네비게이션" | 여러 nav 영역 구분을 위한 라벨입니다. |
aria-current | "page" | 활성 탭에 현재 페이지임을 명시합니다. |
tabIndex | 0 | 키보드 탐색 순서에 포함됩니다. |
사용 방법 예시
CHAART. 앱의 메인 화면에서 Navigation이 사용되는 모습입니다.
추천 작품
인터페이스
Navigation 및 서브 컴포넌트의 Props를 정의합니다.
NavigationProps
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
items | ✓ | — | NavItem[] | 네비게이션 아이템 배열입니다. |
activeIndex | — | 0 | number | 현재 활성화된 탭의 인덱스입니다. |
badge | — | — | Record<number, boolean> | 알림 dot을 표시할 탭 인덱스와 상태입니다. |
maxItems | — | 5 | number | 최대 표시 탭 수 (4~5)입니다. |
Navigation.Item
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
icon | ✓ | — | ReactNode | 탭 아이콘 컴포넌트입니다. |
label | ✓ | — | string | 탭 레이블 텍스트입니다. |
route | ✓ | — | string | 탭 클릭 시 이동할 라우트 경로입니다. |
크기 (Size)
3단계 사이즈 스케일을 지원합니다. 기본값은 md입니다.
상태 (States)
Navigation 탭 아이템의 인터랙션 상태입니다.
반응형 (Responsive)
브레이크포인트별 동작 변화입니다.
overflow-x: scroll
compact tabs visible
full nav, all tabs visible
Components / Navigation
Tab
같은 화면 내에서 콘텐츠 영역을 전환하는 수평 탭 컴포넌트입니다. Fluid/Non-fluid 모드, 2가지 사이즈, 알림 dot(Red Bean)을 지원합니다.
사용법
Tab은 동일 맥락의 콘텐츠 그룹(작품/경력/리뷰 등)을 전환합니다. 활성 탭은 CHAART. Red 하단 인디케이터로 표시됩니다.
<Tab
items={[
{ label: '작품', content: <ArtworkList /> },
{ label: '경력', content: <CareerList /> },
{ label: '리뷰', content: <ReviewList /> },
]}
activeIndex={0}
/>
<div style="display:flex;gap:0;border-bottom:1px solid var(--border)"> <button style="padding:10px 16px;font-family:var(--mono);font-size:var(--text-2xs);font-weight:600;letter-spacing:0.06em;text-transform:uppercase;color:var(--red-brand);border:none;background:none;border-bottom:2px solid var(--red-brand);cursor:pointer">Active</button> <button style="padding:10px 16px;font-family:var(--mono);font-size:var(--text-2xs);font-weight:500;letter-spacing:0.06em;text-transform:uppercase;color:var(--text-muted);border:none;background:none;cursor:pointer">Tab 2</button> </div>
상태 (States)
각 탭 아이템은 5가지 시각 상태를 가집니다. 활성 탭은 하단 인디케이터와 폰트 웨이트로 구분됩니다.
Fluid 모드 (균등 분할)
각 탭이 동일한 너비를 가집니다. 4개 이하 탭에서 사용하세요.
<Tab fluid={true} items={[...]} />
{/* 각 탭이 flex: 1로 균등 분할 */}
{/* 4개 이하 탭에서만 사용 권장 */}
Non-fluid 모드 (텍스트 너비)
각 탭이 텍스트 길이에 맞춰 너비가 결정됩니다. 5개 이상 탭에서 수평 스크롤과 함께 사용합니다.
<Tab fluid={false} itemGap={24} items={[...]} />
{/* 텍스트 너비 + itemGap 간격 */}
{/* 5개 이상은 overflow-x: auto로 스크롤 */}
Size: large / small
large는 메인 탭, small은 서브 필터 등에 사용합니다.
Large (h: 48px, font: 16px)
Small (h: 36px, font: 13px)
<Tab size="large" /> {/* 메인 탭, 기본값 */}
<Tab size="small" /> {/* 서브 필터, 내부 전환 */}
Red Bean (알림 Dot)
새로운 콘텐츠가 있는 탭에 6px 빨간 dot을 표시합니다. 탭 진입 시 자동으로 제거됩니다.
<Tab redBean={[2]} items={[...]} />
{/* index 2 (리뷰) 탭에 알림 dot 표시 */}
{/* 해당 탭 진입 시 dot 자동 제거 */}
접근성
Tab은 WAI-ARIA Tabs 패턴을 따릅니다.
| 속성 | 값 | 설명 |
|---|---|---|
role | "tablist" | 탭 컨테이너의 역할입니다. |
role (item) | "tab" | 개별 탭의 역할입니다. |
aria-selected | "true" | "false" | 탭의 선택 상태입니다. |
aria-controls | "{panelId}" | 탭이 제어하는 패널 ID입니다. |
role (panel) | "tabpanel" | 콘텐츠 패널의 역할입니다. |
사용 방법 예시
작가 프로필 페이지에서 Tab으로 콘텐츠를 전환하는 모습입니다.
박수근
한국화 · 작품 24점
인터페이스
Tab 컴포넌트의 Props를 정의합니다.
TabProps
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
items | ✓ | — | TabItem[] | 탭 아이템 배열. label과 content를 포함합니다. |
size | — | large | large | small | 탭 크기입니다. |
fluid | — | true | boolean | true면 균등 분할, false면 텍스트 너비입니다. |
redBean | — | — | number[] | 알림 dot을 표시할 탭 인덱스 배열입니다. |
itemGap | — | 0 | number (px) | non-fluid 모드에서 탭 간 간격입니다. |
activeIndex | — | 0 | number | 현재 활성화된 탭 인덱스입니다. |
onChange | — | — | (index: number) => void | 탭 변경 핸들러입니다. |
TabItem
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
label | ✓ | — | string | 탭 라벨 텍스트입니다. |
content | ✓ | — | ReactNode | 탭 패널 콘텐츠입니다. |
반응형 (Responsive)
브레이크포인트별 동작 변화입니다.
scroll-x, auto-scroll active
wrap or scroll
full layout, all visible
Components / Navigation
Menu
드롭다운 또는 컨텍스트 메뉴 컴포넌트입니다. 트리거 클릭 시 옵션 목록을 표시하며, 작품 관리(수정/삭제/공유), 정렬 옵션 선택 등에 활용됩니다.
사용법
기본 메뉴는 트리거 버튼과 드롭다운 항목 목록으로 구성됩니다.
<Menu trigger={<Button>옵션</Button>}>
<MenuItem onClick={handleEdit}>작품 수정</MenuItem>
<MenuItem onClick={handleShare}>공유하기</MenuItem>
<MenuItem onClick={handleDuplicate}>복제</MenuItem>
<MenuDivider />
<MenuItem danger onClick={handleDelete}>삭제</MenuItem>
</Menu>
<div style="background:var(--bg-card);border:1px solid var(--border);box-shadow:var(--shadow-lg);min-width:180px;padding:var(--space-1) 0"> <div style="padding:8px 16px;font-size:var(--text-sm);cursor:pointer">Menu Item 1</div> <div style="padding:8px 16px;font-size:var(--text-sm);cursor:pointer;background:var(--alpha-4)">Active Item</div> <div style="height:1px;background:var(--border);margin:var(--space-1) 0"></div> <div style="padding:8px 16px;font-size:var(--text-sm);color:var(--color-danger);cursor:pointer">Delete</div> </div>
아이콘 포함 메뉴
각 메뉴 항목에 아이콘을 추가하여 시각적 인식을 높입니다.
<Menu trigger={<IconButton icon="more" />}>
<MenuItem icon="edit">작품 수정</MenuItem>
<MenuItem icon="share">공유하기</MenuItem>
<MenuItem icon="copy">복제</MenuItem>
<MenuDivider />
<MenuItem icon="archive" disabled>아카이브 (준비 중)</MenuItem>
<MenuDivider />
<MenuItem icon="delete" danger>삭제</MenuItem>
</Menu>
정렬 메뉴
선택형 메뉴로 현재 활성 항목을 표시합니다. 정렬, 필터링 옵션에 적합합니다.
<Menu trigger={<Button>정렬</Button>}>
<MenuLabel>정렬 기준</MenuLabel>
<MenuItem selected value="latest">최신순</MenuItem>
<MenuItem value="price-desc">가격 높은순</MenuItem>
<MenuItem value="price-asc">가격 낮은순</MenuItem>
<MenuItem value="popular">인기순</MenuItem>
</Menu>
접근성
| 속성 | 값 | 설명 |
|---|---|---|
role | menu | 메뉴 컨테이너의 역할을 지정합니다 |
role | menuitem | 각 메뉴 항목의 역할을 지정합니다 |
aria-expanded | boolean | 트리거 버튼의 메뉴 열림/닫힘 상태를 표시합니다 |
aria-haspopup | true | 트리거 버튼이 팝업 메뉴를 가지고 있음을 표시합니다 |
aria-disabled | boolean | 비활성 메뉴 항목에 적용됩니다 |
사용 방법 예시
인터페이스
MenuProps
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
trigger | ✓ | — | ReactNode | 메뉴를 여는 트리거 요소 |
placement | "bottom-start" | "bottom-start" | "bottom-end" | "top-start" | "top-end" | 메뉴 표시 위치 | |
open | — | boolean | 제어 모드에서 열림 상태 제어 | |
onOpenChange | — | (open: boolean) => void | 열림 상태 변경 콜백 | |
closeOnSelect | true | boolean | 항목 선택 시 메뉴 자동 닫기 | |
children | ✓ | — | ReactNode | MenuItem, MenuDivider, MenuLabel 요소 |
크기 (Size)
4단계 사이즈 스케일을 지원합니다. 기본값은 md입니다.
상태 (States)
Menu 및 MenuItem의 인터랙션 상태입니다.
Components / Navigation
Progress Stepper
다단계 프로세스의 현재 위치를 시각적으로 표시하는 컴포넌트입니다. 사용자가 전체 프로세스에서 어느 단계에 있는지 명확하게 안내하며, 완료된 단계와 남은 단계를 구분하여 보여줍니다.
사용법
기본 Stepper는 단계 배열과 현재 단계 인덱스를 받아 진행 상태를 표시합니다. 완료된 단계에는 체크 아이콘이 표시됩니다.
<ProgressStepper
steps={['기본 정보', '작품 사진', '감정 정보', '등록 완료']}
currentStep={1}
/>
<div style="display:flex;align-items:center;gap:var(--space-3)"> <div style="width:28px;height:28px;border-radius:50%;background:var(--red-brand);color:var(--white);display:flex;align-items:center;justify-content:center;font-size:var(--text-xs);font-weight:700">1</div> <div style="flex:1;height:2px;background:var(--red-brand)"></div> <div style="width:28px;height:28px;border-radius:50%;background:var(--red-brand);color:var(--white);display:flex;align-items:center;justify-content:center;font-size:var(--text-xs);font-weight:700">2</div> <div style="flex:1;height:2px;background:var(--alpha-12)"></div> <div style="width:28px;height:28px;border-radius:50%;border:2px solid var(--alpha-12);color:var(--text-muted);display:flex;align-items:center;justify-content:center;font-size:var(--text-xs);font-weight:600">3</div> </div>
방향과 변형
수평(horizontal)과 수직(vertical) 방향을 지원하며, dot 변형으로 간소화된 형태를 제공합니다.
<ProgressStepper
steps={['기본 정보', '작품 사진', '감정 정보']}
currentStep={1}
orientation="vertical"
/>
<ProgressStepper
steps={['1', '2', '3', '4']}
currentStep={1}
variant="dot"
/>
접근성
| 속성 | 값 | 설명 |
|---|---|---|
role | list | 단계 목록임을 명시합니다 |
aria-current | step | 현재 활성 단계를 표시합니다 |
aria-label | string | 전체 스테퍼에 대한 설명을 제공합니다 |
role (각 단계) | listitem | 각 단계가 목록 항목임을 명시합니다 |
aria-disabled | boolean | 클릭 불가능한 미래 단계를 표시합니다 |
사용 방법 예시
작품 등록 프로세스에서 현재 단계를 안내하는 예시입니다.
인터페이스
ProgressStepperProps
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
steps | ✓ | — | string[] | 단계 이름 배열 |
currentStep | ✓ | — | number | 현재 활성 단계 인덱스 (0부터 시작) |
orientation | 'horizontal' | 'horizontal' | 'vertical' | 스테퍼의 방향 | |
variant | 'default' | 'default' | 'dot' | 스테퍼의 시각적 변형 | |
onStepClick | — | (index: number) => void | 단계 클릭 시 호출되는 콜백 |
Components / Navigation
Segmented Control
탭과 유사한 세그먼트 토글 컴포넌트입니다. 제한된 수의 옵션(2~5개) 사이에서 빠르게 전환할 수 있으며, 현재 선택된 항목이 시각적으로 강조됩니다.
사용법
기본 Segmented Control은 세그먼트 배열과 선택 인덱스를 받아 토글 UI를 제공합니다. 선택된 세그먼트는 배경색으로 강조됩니다.
<SegmentedControl
segments={['판매 중', '판매 완료']}
selectedIndex={0}
onChange={handleChange}
/>
<SegmentedControl
segments={['그리드', '리스트', '카드']}
selectedIndex={1}
/>
<div style="display:inline-flex;border:1px solid var(--alpha-12);overflow:hidden"> <button style="padding:8px 20px;font-size:var(--text-xs);font-weight:600;background:var(--charcoal);color:var(--white);border:none;cursor:pointer">Option A</button> <button style="padding:8px 20px;font-size:var(--text-xs);font-weight:500;background:none;color:var(--text);border:none;border-left:1px solid var(--alpha-12);cursor:pointer">Option B</button> </div>
크기와 변형
3가지 크기(sm, md, lg)를 지원합니다. 아이콘을 함께 사용하여 시각적 힌트를 제공할 수 있습니다.
<SegmentedControl segments={['전체', '회화', '조각']} size="sm" />
<SegmentedControl segments={['전체', '회화', '조각']} size="md" />
<SegmentedControl segments={['전체', '회화', '조각']} size="lg" />
접근성
| 속성 | 값 | 설명 |
|---|---|---|
role | tablist | 세그먼트 그룹이 탭 목록임을 명시합니다 |
role (각 세그먼트) | tab | 각 세그먼트가 탭임을 명시합니다 |
aria-selected | boolean | 현재 선택된 세그먼트를 표시합니다 |
aria-label | string | 전체 컨트롤에 대한 설명을 제공합니다 |
키보드 | ArrowLeft / ArrowRight | 화살표 키로 세그먼트 간 이동을 지원합니다 |
사용 방법 예시
작품 목록의 보기 방식을 전환하는 예시입니다.
인터페이스
SegmentedControlProps
| 속성 | 필수 | 기본값 | 타입 | 설명 |
|---|---|---|---|---|
segments | ✓ | — | string[] | 세그먼트 라벨 배열 |
selectedIndex | 0 | number | 현재 선택된 세그먼트 인덱스 | |
size | 'md' | 'sm' | 'md' | 'lg' | 컨트롤의 크기 | |
onChange | — | (index: number) => void | 세그먼트 변경 시 호출되는 콜백 |
크기 (Size)
4단계 사이즈 스케일을 지원합니다. 기본값은 md입니다.
상태 (States)
SegmentedControl 세그먼트의 인터랙션 상태입니다.