# Tabs URL: /lynx/components/tabs Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/tabs.mdx 한 화면의 콘텐츠를 탭 단위로 구분하고, 탭 선택이나 좌우 스와이프로 전환하는 컴포넌트입니다. Lynx Engine 최소 버전: 3.6 사용 XElement: , 사용 가능 버전: @seed-design/lynx-react@0.5.0, @seed-design/lynx-css@0.9.0 ## Preview ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ## Installation - npm: npx @seed-design/cli add ui:tabs - pnpm: pnpm dlx @seed-design/cli add ui:tabs - yarn: yarn dlx @seed-design/cli add ui:tabs - bun: bun x @seed-design/cli add ui:tabs ## Props ### `TabsRoot` ### `TabsList` ### `TabsTrigger` ### `TabsCarousel` ### `TabsContent` ## Usage ```tsx import { TabsCarousel, TabsContent, TabsList, TabsRoot, TabsTrigger, } from "@/components/ui/tabs"; export function App() { return ( 첫 번째 두 번째 첫 번째 콘텐츠 두 번째 콘텐츠 ); } ``` Registry의 `TabsList`는 `Tabs.Indicator`를 자동으로 추가합니다. `TabsCarousel`도 내부에서 `Tabs.CarouselCamera`를 조립하므로 각 `TabsContent`만 자식으로 전달하면 됩니다. `TabsCarousel`은 Lynx 네이티브 ``를 사용합니다. Carousel에 앱 레이아웃에 맞는 높이를 지정해야 하며, `swipeable`을 설정하면 좌우 스와이프로 탭을 바꿀 수 있습니다. iOS에서는 화면 왼쪽 32px 안에서 시작하는 뒤로가기 제스처를 우선합니다. 앱의 제스처 영역과 맞춰야 한다면 `iosBackGestureEdgeWidth`로 너비를 조정하세요. `TabsList`의 `scrollAlign`은 선택한 tab을 가로 목록에 맞추는 방식이며 `"nearest"`, `"start"`, `"center"`, `"end"`를 사용할 수 있습니다. 기본값은 `"start"`로 선택한 tab을 목록 시작에 맞춥니다. Chip Tabs의 `ChipTabsList`는 같은 옵션을 지원하지만 기본값이 `"nearest"`입니다. ## Examples ### Layout Fill (Default) ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Layout Hug ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Size Medium ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Size Small (Default) ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Transition Lynx의 `TabsCarousel`은 탭을 선택할 때 native viewpager의 `selectTab`을 `smooth: false`로 호출하므로 콘텐츠가 즉시 바뀝니다. 전환 효과가 필요하면 `TabsCarousel` 대신 선택 값을 기준으로 콘텐츠를 직접 렌더링하고 앱에서 애니메이션을 적용하세요. ### Swipeable Trigger를 탭하거나 콘텐츠를 좌우로 밀면 선택 값과 Indicator가 함께 이동합니다. ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsCarousel, TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Carousel Prevent Drag Lynx는 `carouselPreventDrag`를 지원하지 않습니다. Carousel 안에서 드래그와 충돌하는 조작 요소는 Carousel 밖에 배치하세요. ### Disabled `disabled` Trigger는 탭할 수 없습니다. Carousel이 비활성 탭에 도달하면 다음 활성 탭으로 이동합니다. ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Notification ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Sticky List 탭이 전체 화면을 차지하고 `TabsList`가 화면 위에 고정되어야 할 때 사용합니다. ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 ); } ``` ### Standalone `TabsContent`를 사용하지 않고 콘텐츠 영역을 직접 구성할 때 사용합니다. 이 구성에서는 Tabs가 제공하는 스와이프 기능을 사용할 수 없습니다. ```tsx import "./styles"; import { useState } from "@lynx-js/react"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [activeTab, setActiveTab] = useState("1"); function handleValueChange(value: string) { "background only"; setActiveTab(value); } return ( 라벨1 라벨2 라벨3 {activeTab === "1" && ( Content 1 )} {activeTab === "2" && ( Content 2 )} {activeTab === "3" && ( Content 3 )} ); } ``` ### Dynamic Height `TabsCarousel`의 `autoHeight`, `lazyMount`, `unmountOnExit` 없이도 `TabsContent`를 Carousel 밖에 배치하면 선택된 콘텐츠 높이에 맞춰 아래 콘텐츠의 위치를 바꿀 수 있습니다. 이 구성은 스와이프를 제공하지 않습니다. ```tsx import "./styles"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); return ( 라벨1 라벨2 라벨3 Content 1 Content 2 Content 3 아래 콘텐츠 ); } ``` ### Scroll to Top `TabsTrigger`의 `bindtap`은 이미 선택된 Trigger를 다시 탭할 때도 호출됩니다. 아래 예시는 네 번째 항목부터 보이도록 시작하며, 선택된 탭을 다시 탭하면 현재 콘텐츠의 `scroll-view`가 맨 위로 이동합니다. ```tsx import "./styles"; import { useCallback, useRef, useState } from "@lynx-js/react"; import type { NodesRef } from "@lynx-js/types"; import { useSeedClassName } from "@seed-design/lynx-react"; import { TabsCarousel, TabsContent, TabsList, TabsRoot, TabsTrigger } from "@/components/ui/tabs"; const SCROLL_ITEMS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; function scrollToTop(ref: NodesRef | null) { "background only"; ref?.invoke({ method: "scrollTo", params: { offset: 0, smooth: true } }).exec(); } export default function Example() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [currentTab, setCurrentTab] = useState("1"); const firstContentRef = useRef(null); const secondContentRef = useRef(null); const setFirstContentRef = useCallback((node: NodesRef | null) => { "background only"; firstContentRef.current = node; }, []); const setSecondContentRef = useCallback((node: NodesRef | null) => { "background only"; secondContentRef.current = node; }, []); function handleFirstTriggerTap() { "background only"; if (currentTab === "1") scrollToTop(firstContentRef.current); } function handleSecondTriggerTap() { "background only"; if (currentTab === "2") scrollToTop(secondContentRef.current); } return ( 라벨1 라벨2 {SCROLL_ITEMS.map((item) => ( Content 1 · {item} ))} {SCROLL_ITEMS.map((item) => ( Content 2 · {item} ))} ); } ``` ## Web Version Differences | 항목 | React Web | Lynx | | --------- | --------------------------------- | ----------------------------------------------- | | 렌더링 요소 | HTML `div`, `button` | 네이티브 `view`, `text`, `scroll-view`, `viewpager` | | 탭 선택 이벤트 | `onClick` 기반 | `bindtap` 기반, 공개 상태 이벤트는 `onValueChange` | | Carousel | Embla Carousel | 네이티브 `` | | 가로 List | CSS overflow | 네이티브 `` | | 제어 방식 | `value`, `defaultValue` | 동일 | | 접근성 | ARIA `tablist`, `tab`, `tabpanel` | Lynx `accessibility-*` 속성 | | Indicator | DOM 측정과 CSS 변수 | `bindlayoutchange` 측정과 viewpager offset 보간 | 스와이프 관성, 비활성 탭 건너뛰기, Indicator 이동은 QR 코드 탭에서 Lynx Explorer로 확인하세요. 문서의 브라우저 미리보기와 네이티브 기기의 제스처 결과가 다를 수 있습니다. ## Unsupported Lynx Features 현재 Lynx 구현은 다음 Web 기능을 지원하지 않습니다. - 세로 `orientation` - 키보드 방향키, Home/End, roving focus - RTL 순서와 스와이프 방향 반전 - `asChild` - `lazyMount`, `unmountOnExit` - Carousel의 `loop`, `autoHeight`, `dragThreshold` - `carouselPreventDrag` - 콘텐츠 전환 애니메이션 Lynx의 native viewpager는 page slot을 유지해야 하므로 Carousel 콘텐츠를 lazy unmount하지 않습니다.