# Bottom Sheet URL: /lynx/components/bottom-sheet Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/bottom-sheet.mdx 화면 하단에서 올라오는 시트 컴포넌트로, 드래그·snap·dismiss 상호작용을 제공합니다. Lynx Engine 최소 버전: 3.6 사용 XElement: 사용 가능 버전: @seed-design/lynx-react@0.1.0, @seed-design/lynx-css@0.1.0 ## Preview ```tsx import "./styles"; import { root, useRef, useState } from "@lynx-js/react"; import { ActionButton, BottomSheet, type BottomSheetRootRef, useSeedClassName, VStack, } from "@seed-design/lynx-react"; function Root() { const seedClassName = useSeedClassName({ colorMode: "system" }); const sheetRef = useRef(null); const [open, setOpen] = useState(false); function handleClose() { "background only"; sheetRef.current?.close(); } return ( {open ? "열림: true" : "열림: false"} Bottom Sheet 열기 Bottom Sheet 배경을 탭하거나 아래로 밀어서도 닫을 수 있어요. 본문 콘텐츠 확인하고 닫기 ); } root.render(); ``` ## Installation - npm: npx @seed-design/cli add ui:bottom-sheet - pnpm: pnpm dlx @seed-design/cli add ui:bottom-sheet - yarn: yarn dlx @seed-design/cli add ui:bottom-sheet - bun: bun x @seed-design/cli add ui:bottom-sheet `@seed-design/lynx-react`의 `BottomSheet`는 [`@lynx-js/lynx-ui-sheet`](https://github.com/lynx-family/lynx-ui/tree/main/packages/lynx-ui-sheet)를 래핑합니다. 드래그·스프링·snap·presence 로직은 lynx-ui-sheet가 담당하고, SEED는 공개 API와 recipe 슬롯 스타일만 제공합니다. `@seed-design/lynx-react`를 설치하면 `@lynx-js/lynx-ui-sheet`도 함께 설치됩니다. ## Usage ```tsx import { BottomSheetBody, BottomSheetContent, BottomSheetFooter, BottomSheetRoot, BottomSheetTrigger, } from "@/components/ui/bottom-sheet"; export function App() { return ( 시트 열기 본문 콘텐츠 하단 액션 영역 ); } ``` snippet의 `BottomSheetContent`는 내부에서 Positioner, Backdrop, Content, Header, Title, Description을 조립합니다. `BottomSheetTrigger`는 Content 밖에 두어야 탭으로 시트를 열 수 있습니다. 본문 콘텐츠가 길어지는 경우 `BottomSheetBody`에 넣으면 세로 스크롤 영역으로 렌더링됩니다. 스크롤 가능한 긴 본문에서는 `BottomSheetRoot`에 `handleOnly`를 함께 사용해 시트 드래그를 handle로 제한하고, 본문 위의 세로 제스처는 body 스크롤에 사용하세요. ```tsx 긴 본문 열기 {/* 긴 본문 */} ``` ### Imperative 제어 `BottomSheetRoot`에 `ref`를 전달하면 `open`, `close`, `snapTo`, `expand`, `collapse` 메서드를 호출할 수 있습니다. ```tsx import { useRef } from "@lynx-js/react"; import { BottomSheetRoot, type BottomSheetRootRef } from "@/components/ui/bottom-sheet"; export function App() { const ref = useRef(null); return ( {/* ... */} ); } ``` ## Props ### `BottomSheetRoot` ### `BottomSheetTrigger` ### `BottomSheetContent` ### `BottomSheetBody` ### `BottomSheetFooter` ## Examples ### Trigger `BottomSheet.Trigger`를 탭하면 Bottom Sheet가 열립니다. Lynx에서는 `asChild`를 지원하지 않으므로 Trigger가 자식 요소를 감싸는 ``를 렌더링합니다. Footer의 버튼, 배경 탭, 아래 방향 드래그로 Bottom Sheet를 닫을 수 있습니다. 예제 위쪽의 상태 값에서 `onOpenChange` 결과를 확인할 수 있습니다. ## Preview ```tsx import "./styles"; import { root, useRef, useState } from "@lynx-js/react"; import { ActionButton, BottomSheet, type BottomSheetRootRef, useSeedClassName, VStack, } from "@seed-design/lynx-react"; function Root() { const seedClassName = useSeedClassName({ colorMode: "system" }); const sheetRef = useRef(null); const [open, setOpen] = useState(false); function handleClose() { "background only"; sheetRef.current?.close(); } return ( {open ? "열림: true" : "열림: false"} Bottom Sheet 열기 Bottom Sheet 배경을 탭하거나 아래로 밀어서도 닫을 수 있어요. 본문 콘텐츠 확인하고 닫기 ); } root.render(); ``` ### Controlled Trigger 외의 방식으로 Bottom Sheet를 열고 닫으려면 `open`과 `onOpenChange`로 상태를 제어합니다. 배경 탭이나 드래그로 닫을 때도 `onOpenChange`가 호출되므로 전달받은 값을 `open`에 반영해야 합니다. ```tsx import "./styles"; import { root, useState } from "@lynx-js/react"; import { ActionButton, BottomSheet, useSeedClassName, VStack } from "@seed-design/lynx-react"; function Root() { const seedClassName = useSeedClassName({ colorMode: "system" }); const [open, setOpen] = useState(false); function handleOpen() { "background only"; setOpen(true); } function handleClose() { "background only"; setOpen(false); } return ( {open ? "open: true" : "open: false"} 상태로 열기 Controlled Bottom Sheet open 값과 onOpenChange로 열린 상태를 제어합니다. {open ? "현재 open: true" : "현재 open: false"} 상태로 닫기 ); } root.render(); ``` ### Snap Points `snapPoints`에는 픽셀 숫자, 화면 높이 기준 백분율, 콘텐츠 크기에 맞추는 `"fit"`을 사용할 수 있습니다. 배열 순서가 snap index가 되며, `initialSnap`으로 처음 열릴 높이를 정합니다. `onSnapChange`에서는 현재 index와 픽셀로 계산된 높이를 받을 수 있습니다. ref의 `snapTo` 메서드를 사용하면 원하는 높이로 이동할 수 있습니다. ```tsx import "./styles"; import { root, useRef, useState } from "@lynx-js/react"; import { ActionButton, BottomSheet, type BottomSheetRootRef, HStack, useSeedClassName, VStack, } from "@seed-design/lynx-react"; const snapPoints = ["45%", "80%"]; function Root() { const seedClassName = useSeedClassName({ colorMode: "system" }); const sheetRef = useRef(null); const [snapIndex, setSnapIndex] = useState(0); function handleSnapToFirst() { "background only"; sheetRef.current?.snapTo(0); } function handleSnapToSecond() { "background only"; sheetRef.current?.snapTo(1); } function handleClose() { "background only"; sheetRef.current?.close(); } return ( snap index: {JSON.stringify(snapIndex)} setSnapIndex(index)} > Snap Points 열기 Snap Points 핸들을 드래그하거나 버튼을 탭해 높이를 바꿔 보세요. 현재 snap index: {JSON.stringify(snapIndex)} 45% 80% 닫기 ); } root.render(); ``` 드래그와 snap 애니메이션의 최종 동작은 QR 코드 탭에서 Lynx Explorer로 확인하세요. 문서 미리보기에서는 기기와 제스처 처리 결과가 다를 수 있습니다. ## 웹 버전과의 차이 Lynx `BottomSheet`는 React `BottomSheet`와 다음과 같은 차이가 있습니다. - **이벤트 핸들링**: `onClick` 대신 `bindtap`을 사용합니다. - **렌더링 요소**: HTML `
` 대신 네이티브 ``/`` 요소를 렌더링합니다. - **내부 엔진**: Radix `Drawer` 대신 `@lynx-js/lynx-ui-sheet`를 래핑합니다. | 항목 | 웹 | Lynx | | -------------------------------- | ----------------------------------- | ----------------------------------------------------------------------- | | 상태 prop | `open`/`defaultOpen`/`onOpenChange` | 동일 (내부에서 lynx-ui-sheet의 `show`/`defaultShow`/`onShowChange`로 매핑) | | `onOpenChange`의 `details.reason` | 지원 | **미지원** — 변경된 `open` 값만 전달 | | 마운트 제어 | `lazyMount`, `unmountOnExit` | **미지원** — `forceMount`로 대체 | | `BottomSheetPositioner` | 존재 | 동일 (lynx-ui-sheet의 `SheetView` 래핑) — 첫 open 전까지 Backdrop/Content 마운트 지연 | | `BottomSheetCloseButton` | 존재 | **미포함** — Tier B (SVG 지원 후 추가 예정) | | `snapPoints` | 지원 | 지원 (`(number \| string)[]`, 기본값 `["fit"]`) | | `BottomSheetTrigger`의 `asChild` | 지원 | **미지원** (기본 ``로만 렌더링) | | imperative ref API | — | `BottomSheetRootRef.{open, close, snapTo, expand, collapse}` | ## Lynx 미지원 기능 현재 Lynx 플랫폼 제약으로 다음 기능이 지원되지 않습니다. ### Lynx SVG 지원 후 추가 예정 | 기능 | 웹 대응 | 설명 | | ------------------------ | ------------------------- | ------------ | | `BottomSheetCloseButton` | `BottomSheet.CloseButton` | X 아이콘 SVG 필요 | ### 마운트 수명주기 | 기능 | 웹 대응 | 대체 방법 | | --------------- | ----------------------- | --------------------------------------------------------------- | | `lazyMount` | `BottomSheet.Root` prop | `forceMount={false}` (기본값) — 기본적으로 열릴 때만 마운트 | | `unmountOnExit` | `BottomSheet.Root` prop | `forceMount`로 제어 — lynx-ui-sheet의 presence 상태 머신이 자연스럽게 언마운트 처리 |