# Chip Tabs URL: /lynx/components/chip-tabs Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/chip-tabs.mdx Chip 형태로 표현된 탭 컴포넌트입니다. 카테고리나 필터를 선택하여 콘텐츠를 전환할 때 사용합니다. Lynx Engine 최소 버전: 3.9 사용 XElement: , 사용 가능 버전: @seed-design/lynx-react@0.8.0, @seed-design/lynx-css@0.12.0 ## Preview ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { useSeedClassName } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "@/components/ui/chip-tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [value, setValue] = useState("1"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } return ( 라벨1 라벨2 라벨3 {value === "1" && content 1} {value === "2" && content 2} {value === "3" && content 3} ); } ``` ## Installation - npm: npx @seed-design/cli add ui:chip-tabs - pnpm: pnpm dlx @seed-design/cli add ui:chip-tabs - yarn: yarn dlx @seed-design/cli add ui:chip-tabs - bun: bun x @seed-design/cli add ui:chip-tabs ## Props ### `ChipTabsRoot` ### `ChipTabsList` ### `ChipTabsTrigger` ### `ChipTabsCarousel` ### `ChipTabsContent` ## Usage ```tsx import { useState } from "@lynx-js/react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger, } from "@/components/ui/chip-tabs"; export function App() { const [value, setValue] = useState("1"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } return ( <> 라벨1 라벨2 라벨3 {value === "1" && content 1} {value === "2" && content 2} {value === "3" && content 3} ); } ``` `ChipTabsRoot`는 `value`와 `onValueChange`로 제어하거나 `defaultValue`로 초기 선택 값을 지정합니다. `ChipTabsTrigger`의 `notification` boolean은 label 옆 6px 간격에 작은 `NotificationBadge`를 표시합니다. `ChipTabsCarousel`은 Registry에서 `ChipTabsCarouselCamera`를 조립하므로 `ChipTabsContent`만 자식으로 전달합니다. Carousel은 native ``이므로 Carousel 또는 Camera에 앱 레이아웃에 맞는 높이를 명시하고, `swipeable`로 좌우 스와이프를 켭니다. iOS의 화면 왼쪽 가장자리 뒤로가기 제스처를 우선할 범위는 `iosBackGestureEdgeWidth`로 정합니다. 공개 package API에서 Camera의 native `bindchange`, `bindwillchange`, `bindoffsetchange`를 직접 사용해야 할 때는 `ChipTabs.CarouselCamera`를 조합합니다. `onSwipeStart`, `onSwipeEnd`, `onSettle`은 Carousel에 전달해 스와이프 시작·종료·정착을 처리할 수 있습니다. ```tsx import { useState } from "@lynx-js/react"; import { ChipTabs } from "@seed-design/lynx-react"; export function App() { const [lastEvent, setLastEvent] = useState("대기"); function handleSwipeStart() { "background only"; setLastEvent("스와이프 시작"); } function handleSwipeEnd() { "background only"; setLastEvent("스와이프 종료"); } function handleSettle() { "background only"; setLastEvent("정착"); } return ( 라벨1 라벨2 content 1 content 2 {lastEvent} ); } ``` ## Scroll Alignment `ChipTabsList`의 `scrollAlign`은 선택한 chip을 가로 목록에 맞추는 방식입니다. `"nearest"`, `"start"`, `"center"`, `"end"`를 사용할 수 있으며 기본값은 `"nearest"`입니다. - `"start"`, `"center"`, `"end"`는 선택한 chip을 각각 목록의 시작, 가운데, 끝에 맞춥니다. - `"nearest"`는 React Web의 최소 스크롤 동작과 같이 선택한 chip이 완전히 보이면 현재 위치를 유지합니다. 왼쪽이 잘렸을 때는 시작 경계까지, 오른쪽이 잘렸을 때는 끝 경계까지 필요한 만큼만 이동하므로 `"end"`처럼 항상 끝에 맞추지 않습니다. 실제 chip의 너비와 위치, viewport를 기준으로 계산하며, 가변 너비 chip도 그대로 유지합니다. 목록의 첫·마지막 항목을 가운데에 맞추기 위해 빈 공간을 추가하지 않고 스크롤 가능한 범위 안에서만 이동하며 항목 순서도 바꾸지 않습니다. 아래 예제에서 모드를 고르면 선택을 `라벨1`로 초기화합니다. 그 뒤 `라벨6`을 탭하거나 `6번 선택`을 탭해 controlled `value` 변경에서도 실제 scrollAlign 차이를 확인하세요. `nearest`를 고르면 `scrollAlign` prop을 생략하여 기본 동작도 함께 확인합니다. ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { ActionButton, Box, HStack, Text, useSeedClassName, VStack } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger, type ChipTabsListProps, } from "@/components/ui/chip-tabs"; type ScrollAlign = NonNullable; const ALIGNMENTS: ScrollAlign[] = ["nearest", "start", "center", "end"]; const LABELS = Array.from({ length: 15 }, (_, index) => String(index + 1)); export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [value, setValue] = useState("1"); const [scrollAlign, setScrollAlign] = useState("nearest"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } function selectScrollAlign(nextScrollAlign: ScrollAlign) { "background only"; setScrollAlign(nextScrollAlign); setValue("1"); } function selectSixthChip() { "background only"; setValue("6"); } return ( {ALIGNMENTS.map((nextScrollAlign) => ( { "background only"; selectScrollAlign(nextScrollAlign); }} > {nextScrollAlign} ))} 6번 선택 {`선택: 라벨${value}`} {LABELS.map((label) => ( {`라벨${label}`} ))} ); } ``` `nearest`에서 선택한 chip이 이미 완전히 보인 상태로 다시 선택하면 이동하지 않습니다. 목록을 직접 스크롤한 뒤에도 다음 선택이 필요한 경우에만 이동합니다. ## Examples ### Size=Medium `medium`의 최소 높이는 36px입니다. ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { useSeedClassName } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "@/components/ui/chip-tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [value, setValue] = useState("1"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } return ( 라벨1 라벨2 라벨3 {value === "1" && content 1} {value === "2" && content 2} {value === "3" && content 3} ); } ``` ### Size=Large `large`의 최소 높이는 40px입니다. ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { useSeedClassName } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "@/components/ui/chip-tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [value, setValue] = useState("1"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } return ( 라벨1 라벨2 라벨3 {value === "1" && content 1} {value === "2" && content 2} {value === "3" && content 3} ); } ``` ### Variant=Neutral Solid ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { useSeedClassName } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "@/components/ui/chip-tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [value, setValue] = useState("1"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } return ( 라벨1 라벨2 라벨3 {value === "1" && content 1} {value === "2" && content 2} {value === "3" && content 3} ); } ``` ### Variant=Neutral Outline ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { useSeedClassName } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "@/components/ui/chip-tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [value, setValue] = useState("1"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } return ( 라벨1 라벨2 라벨3 {value === "1" && content 1} {value === "2" && content 2} {value === "3" && content 3} ); } ``` ### Notification ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { useSeedClassName } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "@/components/ui/chip-tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [value, setValue] = useState("1"); function handleValueChange(nextValue: string) { "background only"; setValue(nextValue); } return ( 라벨1 라벨2 라벨3 {value === "1" && content 1} {value === "2" && content 2} {value === "3" && content 3} ); } ``` ### With Scroll Fog `ScrollFog`는 Registry snippet이나 Recipe 없이 `@seed-design/lynx-react` 패키지에서 직접 제공되는 package-only 공개 컴포넌트입니다. `ChipTabsList`를 `placement={["left", "right"]}`인 `ScrollFog`로 감싸면 기본 20px fog가 좌우 edge에 표시됩니다. List의 내부 좌우 padding 16px은 그대로 유지합니다. ```tsx import "./styles"; import { ScrollFog, useSeedClassName } from "@seed-design/lynx-react"; import { ChipTabsList, ChipTabsRoot, ChipTabsTrigger } from "@/components/ui/chip-tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 라벨4 라벨5 라벨6 라벨7 라벨8 라벨9 라벨10 라벨11 라벨12 라벨13 라벨14 라벨15 ); } ``` ## Web Version Differences | 항목 | React Web | Lynx | | -------- | ------------------------- | ------------------------------------------------ | | 렌더링 요소 | HTML `div`, `button` | 네이티브 `view`, `text`, `scroll-view`, `viewpager` | | 탭 선택 이벤트 | `onClick` 기반 | `bindtap` 기반, 공개 상태 이벤트는 `onValueChange` | | 콘텐츠 | Root 바깥의 조건부 `div`도 사용 가능 | Root 바깥에서 value에 따른 조건부 native `text`를 렌더링할 수 있음 | | 가로 List | CSS overflow | 네이티브 가로 `scroll-view` | | Carousel | 웹 Carousel | 네이티브 `` | React 예제와 마찬가지로 Lynx에서도 Root 바깥에서 선택 값에 따라 조건부 native `text` 콘텐츠를 렌더링할 수 있습니다. 별도 AppBar, 화면 shell, CTA, asset은 제공하지 않습니다. ## Unsupported Lynx Features - 키보드 포커스와 roving focus - RTL 순서와 스와이프 방향 반전 - `lazyMount`, `unmountOnExit`: native viewpager는 모든 page slot을 유지합니다. - Carousel의 `loop`, `autoHeight`, `dragThreshold`, `carouselPreventDrag` - 콘텐츠 전환 애니메이션