# Select URL: /lynx/components/select Source: https://github.com/daangn/seed-design/blob/dev/docs/content/lynx/components/select.mdx 트리거를 눌러 목록에서 값을 선택하는 컴포넌트입니다. Lynx Engine 최소 버전: 3.9 사용 XElement: 사용 가능 버전: @seed-design/lynx-react@0.8.0, @seed-design/lynx-css@0.12.0 ## Preview ```tsx import { Box } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ## Installation - npm: npx @seed-design/cli@latest add ui:select - pnpm: pnpm dlx @seed-design/cli@latest add ui:select - yarn: yarn dlx @seed-design/cli@latest add ui:select - bun: bun x @seed-design/cli@latest add ui:select ## Usage `SelectContent`는 긴 목록을 스크롤합니다. `container`를 지정하지 않으면 Lynx view 안에, 지정하면 native overlay에 렌더링됩니다. ```tsx import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export function App() { return ( ); } ``` - `multiple`일 때는 항목을 탭해도 목록을 닫지 않고 선택 상태를 전환합니다. - `SelectRoot`의 문자열 `label` 또는 `accessibility-label`은 Trigger의 기본 접근성 이름으로 사용됩니다. - 목록을 열면 선택된 항목이 보이도록 스크롤하며, Trigger 주변의 여유 공간과 safe area에 맞춰 높이를 제한합니다. ## Props ### `SelectRoot` ### `SelectTrigger` ### `SelectContent` ### `SelectGroup` ### `SelectItem` ## Examples ### Size `size`로 trigger와 목록의 크기를 정합니다. 기본값은 `large`이고, `responsive`는 native runtime의 화면 폭이 1280 이상이면 `medium`, 그 외에는 `large`로 해석합니다. ```tsx import { Box, VStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ### Groups `SelectGroup`으로 옵션을 묶고 `label`로 그룹 제목을 표시합니다. 두 번째 그룹부터는 그룹 사이에 구분선이 추가됩니다. ```tsx import { Box } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ### Multiple Selection `multiple`을 지정하면 선택한 옵션을 다시 탭해 해제할 수 있습니다. 다중 선택 중에는 목록이 닫히지 않으며 trigger에는 기본적으로 선택된 `textValue`가 `", "`로 연결되어 표시됩니다. ```tsx import { Box } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ### With Description `SelectItem`의 `description`으로 옵션에 부가 설명을 추가합니다. ```tsx import { Box } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ### With Prefix Icon `SelectItem`의 `prefixIcon`으로 옵션 앞에 아이콘을 표시합니다. 단일 옵션이 선택되면 해당 아이콘이 trigger prefix에 표시됩니다. 선택이 없거나 여러 옵션이 선택된 경우에는 `SelectTrigger`의 `prefixIcon` fallback을 사용합니다. ```tsx import IconGlobeLine from "@karrotmarket/lynx-monochrome-icon/IconGlobeLine"; import IconLockLine from "@karrotmarket/lynx-monochrome-icon/IconLockLine"; import IconPerson2Line from "@karrotmarket/lynx-monochrome-icon/IconPerson2Line"; import IconPersonLine from "@karrotmarket/lynx-monochrome-icon/IconPersonLine"; import { Box } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( } /> } /> } /> } /> ); } ``` ### Custom Item Label `label`에는 복합 JSX를 전달할 수 있습니다. 복합 label을 사용할 때는 trigger에 표시할 문자열과 옵션 접근성 이름을 위해 `textValue`를 함께 지정하세요. ```tsx import IconCarLine from "@karrotmarket/lynx-monochrome-icon/IconCarLine"; import IconFigureBikeLine from "@karrotmarket/lynx-monochrome-icon/IconFigureBikeLine"; import IconMetroFrontsideLine from "@karrotmarket/lynx-monochrome-icon/IconMetroFrontsideLine"; import { Box, HStack } from "@seed-design/lynx-react"; import { Badge } from "@/components/ui/badge"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( } /> } label={ 지하철 가장 빠름 } /> } label={ 자동차 고객지원에 문의 } /> ); } ``` ### Disabled `SelectItem`의 `disabled`는 특정 옵션을, `SelectRoot`의 `disabled`는 Select 전체를 비활성화합니다. `readOnly`도 목록을 열거나 선택을 바꾸지 않습니다. ```tsx import { Box, VStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ### Long List 목록은 상하 8px 패딩을 포함한 내용 높이만큼 표시하며, 별도의 최소 높이는 없습니다. 최대 높이는 480px이고 trigger 주위의 남은 공간과 safe area에 맞춰 제한합니다. 내용이 이 높이를 넘을 때만 스크롤됩니다. 열릴 때 선택된 옵션이 화면 밖에 있으면 보이는 데 필요한 만큼만 스크롤합니다. 이미 보이는 옵션을 선택하거나 목록이 닫히는 중에는 스크롤 위치를 바꾸지 않습니다. ```tsx import { Box } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; const timeSlots = Array.from({ length: 48 }, (_, index) => { const hour = String(Math.floor(index / 2)).padStart(2, "0"); return `${hour}:${index % 2 === 0 ? "00" : "30"}`; }); export default function Example() { return ( {timeSlots.map((slot) => ( ))} ); } ``` ### Placement `placement`로 목록 위치를 정합니다. 기본값은 `bottom`이며, 공간이 부족하면 native positioner가 반대쪽으로 뒤집거나 경계 안으로 이동합니다. ```tsx import { Box } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ### Listening to Value Changes `value`와 `onValueChange`로 선택 값을 제어할 수 있습니다. ```tsx import { useState } from "@lynx-js/react"; import { Box, VStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { const [value, setValue] = useState(["apple"]); return ( 선택된 값: {value.length > 0 ? value.join(", ") : "없음"} ); } ``` ### Controlled Open State `open`과 `onOpenChange`로 목록 상태를 제어합니다. 초기 상태만 지정하려면 `defaultOpen`을 사용하세요. ```tsx import { useState } from "@lynx-js/react"; import { ActionButton, Box, VStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { const [open, setOpen] = useState(false); return ( setOpen(true)}> 목록 열기 목록 상태: {open ? "열림" : "닫힘"} ); } ``` ### Container `container`를 지정하지 않으면 Select 레이어는 Lynx view 안의 `position: fixed` view로 렌더링되고, z-index `99`로 같은 화면의 형제 요소 위에 그려집니다. `container="window"`처럼 native container 이름을 지정하면 native overlay에 렌더링되어 Lynx view 밖의 host 화면까지 덮습니다. `overlayLevel`은 `container`를 지정했을 때 overlay의 표시 순서를 정합니다. 두 방식 모두 레이어 바깥을 탭하면 아래 화면에 탭을 전달하지 않고 `"interactOutside"` reason으로 닫힙니다. 닫힌 동안에도 레이어를 숨긴 채 유지하므로 열기 전에도 `defaultValue`의 label과 아이콘을 trigger에 표시합니다. ```tsx import { useState } from "@lynx-js/react"; import { ActionButton, Box, Text, VStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { const [closeReason, setCloseReason] = useState(null); const [count, setCount] = useState(0); function handleOpenChange(open: boolean, details: { reason: string }) { "background only"; if (!open) setCloseReason(details.reason); } function handleUnderlyingTap() { "background only"; setCount((current) => current + 1); } return ( {`아래 버튼 ${count}`} {`마지막 닫힘 이유: ${closeReason ?? "-"}`} ); } ``` ### `onOpenChange` Details `onOpenChange`의 두 번째 인자는 상태를 바꾼 이유와 native event를 제공합니다. - 열림: `"trigger"` - 닫힘: `"trigger"`, `"itemSelect"`, `"interactOutside"` - `container`를 지정한 native overlay가 닫힘을 요청한 경우(Android 뒤로 가기): `"dismiss"`. `container`가 없으면 전달하지 않으며, Android 뒤로 가기는 Select가 아니라 host 화면을 닫습니다. ```tsx import { useState } from "@lynx-js/react"; import { Box, HStack, VStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { const [open, setOpen] = useState(false); const [openReason, setOpenReason] = useState(null); const [closeReason, setCloseReason] = useState(null); function handleOpenChange(nextOpen: boolean, details: { reason: string }) { "background only"; setOpen(nextOpen); (nextOpen ? setOpenReason : setCloseReason)(details.reason); } return ( 마지막 열림 이유: {openReason ?? "-"} 마지막 닫힘 이유: {closeReason ?? "-"} ); } ``` ### Custom Value Format `formatValue`로 trigger에 표시할 선택 값을 바꿉니다. callback은 `value`, `label`, `textValue`, `icon`, `resolved`를 담은 선택 item 배열을 받습니다. `icon`은 `SelectItem`의 `prefixIcon`이고, 목록에 없는 값은 `resolved: false`로 전달합니다. ```tsx import { Box, HStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( items.map((item) => item.textValue).join(", ")} > rest.length > 0 ? `${first?.textValue ?? ""} 외 ${rest.length}개` : first?.textValue } > ); } ``` ### Field Integration `SelectRoot`는 기존 Lynx `Field`를 조합하므로 `label`, `description`, `errorMessage`, `invalid`, `required`와 `showRequiredIndicator`를 지원합니다. 오류 메시지가 있으면 description 대신 오류 메시지를 표시합니다. ```tsx import { Box, VStack } from "@seed-design/lynx-react"; import { SelectContent, SelectGroup, SelectItem, SelectRoot, SelectTrigger, } from "@/components/ui/select"; export default function Example() { return ( ); } ``` ### Form Integration Lynx에는 HTML `
`과 native `