트리거를 눌러 native overlay의 목록에서 값을 선택하는 컴포넌트입니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "240px" >
< SelectRoot defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일을 선택하세요" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
npx @seed-design/cli@latest add ui:select pnpm dlx @seed-design/cli@latest add ui:select yarn dlx @seed-design/cli@latest add ui:select bun x @seed-design/cli@latest add ui:select
의존성 설치
npm install @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react yarn add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react pnpm add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react bun add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react 아래 코드를 복사 후 붙여넣고 사용하세요 /**
* @file ui:select
* @requires @seed-design/lynx-react@>=0.8.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.12.0 <1.0.0
* @requires @karrotmarket/lynx-monochrome-icon@>=1.20.0 <2.0.0
**/
import IconCheckmarkFatFill from "@karrotmarket/lynx-monochrome-icon/IconCheckmarkFatFill" ;
import IconChevronDownSmallLine from "@karrotmarket/lynx-monochrome-icon/IconChevronDownSmallLine" ;
import * as React from "@lynx-js/react" ;
import { Field as SeedField, Select as SeedSelect, mergeProps } from "@seed-design/lynx-react" ;
import type { LynxAccessibilityProps, LynxIconElementProps } from "@seed-design/lynx-react" ;
type FieldRootRef = React . ComponentRef < typeof SeedField.Root>;
const SelectAccessibilityLabelContext =
React. createContext < LynxAccessibilityProps [ "accessibility-label" ]>( undefined );
export interface SelectRootProps
extends SeedSelect . RootProps ,
Pick < SeedField . RootProps , "required" | "invalid" | "readOnly" > {
label ?: React . ReactNode ;
labelWeight ?: SeedField . LabelProps [ "weight" ];
indicator ?: React . ReactNode ;
showRequiredIndicator ?: boolean ;
description ?: React . ReactNode ;
errorMessage ?: React . ReactNode ;
fieldRef ?: React . Ref < FieldRootRef >;
"accessibility-label" ?: LynxAccessibilityProps [ "accessibility-label" ];
}
/**
* Field와 Select의 값·열림 상태를 함께 조합합니다. label이 문자열이 아니면
* `accessibility-label`을 `SelectRoot` 또는 `SelectTrigger`에 지정하세요.
*
* @see https://seed-design.io/lynx/components/select
*/
export const SelectRoot = React. forwardRef < FieldRootRef , SelectRootProps >(
(
{
children,
label,
labelWeight,
indicator,
showRequiredIndicator,
description,
errorMessage,
fieldRef,
"accessibility-label" : accessibilityLabel,
value,
defaultValue,
onValueChange,
multiple,
open,
defaultOpen,
onOpenChange,
disabled,
required,
invalid,
readOnly,
size,
placement,
gutter,
overflowPadding,
formatValue,
... fieldProps
},
ref,
) => {
const mergedRefProps = React. useMemo (
() => mergeProps ({ ref }, { ref: fieldRef }),
[ref, fieldRef],
);
const renderHeader = label != null || indicator != null ;
const renderErrorMessage = invalid && errorMessage != null ;
const renderDescription = description != null && ! renderErrorMessage;
const renderFooter = renderDescription || renderErrorMessage;
const defaultAccessibilityLabel = typeof label === "string" ? label : undefined ;
const resolvedAccessibilityLabel = accessibilityLabel ?? defaultAccessibilityLabel;
return (
< SeedField.Root
{ ... mergedRefProps}
required = {required}
disabled = {disabled}
invalid = {invalid}
readOnly = {readOnly}
{ ... fieldProps}
>
{renderHeader ? (
< SeedField.Header >
< SeedField.Label weight = {labelWeight}>
{label}
{showRequiredIndicator ? < SeedField.RequiredIndicator /> : null }
{indicator != null ? (
< SeedField.IndicatorText >{indicator}</ SeedField.IndicatorText >
) : null }
</ SeedField.Label >
</ SeedField.Header >
) : null }
< SeedSelect.Root
value = {value}
defaultValue = {defaultValue}
onValueChange = {onValueChange}
multiple = {multiple}
open = {open}
defaultOpen = {defaultOpen}
onOpenChange = {onOpenChange}
disabled = {disabled}
required = {required}
invalid = {invalid}
readOnly = {readOnly}
size = {size}
placement = {placement}
gutter = {gutter}
overflowPadding = {overflowPadding}
formatValue = {formatValue}
>
< SelectAccessibilityLabelContext.Provider value = {resolvedAccessibilityLabel}>
{children}
</ SelectAccessibilityLabelContext.Provider >
</ SeedSelect.Root >
{renderFooter ? (
< SeedField.Footer >
{renderDescription ? (
< SeedField.Description >{description}</ SeedField.Description >
) : null }
{renderErrorMessage ? (
< SeedField.ErrorMessage >{errorMessage}</ SeedField.ErrorMessage >
) : null }
</ SeedField.Footer >
) : null }
</ SeedField.Root >
);
},
);
SelectRoot.displayName = "SelectRoot" ;
export interface SelectTriggerProps extends Omit < SeedSelect . TriggerProps , "children" > {
placeholder ?: React . ReactNode ;
prefixIcon ?: React . ReactElement < LynxIconElementProps >;
suffixIcon ?: React . ReactElement < LynxIconElementProps >;
}
/**
* 선택 값 또는 placeholder를 표시하고 탭으로 목록을 열고 닫습니다.
*
* @see https://seed-design.io/lynx/components/select
*/
export const SelectTrigger = React. forwardRef < unknown , SelectTriggerProps >(
(
{
placeholder,
prefixIcon,
suffixIcon = < IconChevronDownSmallLine />,
"accessibility-label" : accessibilityLabel,
... props
},
ref,
) => {
const rootAccessibilityLabel = React. useContext (SelectAccessibilityLabelContext);
return (
< SeedSelect.Trigger
ref = {ref}
accessibility-label = {accessibilityLabel ?? rootAccessibilityLabel}
{ ... props}
>
< SeedSelect.PrefixIcon fallback = {prefixIcon} />
< SeedSelect.Value />
{placeholder != null ? (
< SeedSelect.Placeholder >{placeholder}</ SeedSelect.Placeholder >
) : null }
< SeedSelect.SuffixIcon icon = {suffixIcon} />
</ SeedSelect.Trigger >
);
},
);
SelectTrigger.displayName = "SelectTrigger" ;
export interface SelectContentProps extends SeedSelect . ContentProps {}
/**
* Native overlay, 위치 계산, 표시 수명과 긴 목록의 scroll viewport를 함께 구성합니다.
* 자식에 별도 Positioner나 ScrollArea를 추가하지 마세요.
*
* @see https://seed-design.io/lynx/components/select
*/
export const SelectContent = SeedSelect.Content;
export interface SelectGroupProps extends SeedSelect . GroupProps {
label ?: React . ReactNode ;
}
export const SelectGroup = React. forwardRef < unknown , SelectGroupProps >(
({ label , children , ... props }, ref ) => {
return (
< SeedSelect.Group ref = {ref} { ... props}>
{label != null ? < SeedSelect.GroupLabel >{label}</ SeedSelect.GroupLabel > : null }
{children}
</ SeedSelect.Group >
);
},
);
SelectGroup.displayName = "SelectGroup" ;
export interface SelectItemProps extends Omit < SeedSelect . ItemProps , "children" > {
label : React . ReactNode ;
description ?: React . ReactNode ;
selectedIndicator ?: React . ReactNode ;
prefixIcon ?: React . ReactElement < LynxIconElementProps >;
}
/**
* item body, 설명, prefix icon과 선택 indicator를 조합합니다. `selectedIndicator`로
* 기본 checkmark를 바꿀 수 있습니다.
*
* @see https://seed-design.io/lynx/components/select
*/
export const SelectItem = React. forwardRef < unknown , SelectItemProps >(
(
{
label,
description,
selectedIndicator = < IconCheckmarkFatFill />,
prefixIcon,
"accessibility-label" : accessibilityLabel,
... props
},
ref,
) => {
return (
< SeedSelect.Item
ref = {ref}
accessibility-label = {accessibilityLabel ?? ( typeof label === "string" ? label : undefined )}
prefixIcon = {prefixIcon}
label = {label}
{ ... props}
>
< SeedSelect.ItemPrefixIcon />
< SeedSelect.ItemBody >
< SeedSelect.ItemLabel >{label}</ SeedSelect.ItemLabel >
{description != null ? (
< SeedSelect.ItemDescription >{description}</ SeedSelect.ItemDescription >
) : null }
</ SeedSelect.ItemBody >
< SeedSelect.ItemIndicator selected = {selectedIndicator} />
</ SeedSelect.Item >
);
},
);
SelectItem.displayName = "SelectItem" ;
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
SelectContent는 native overlay, 위치 계산, 표시 수명과 긴 목록의 scroll viewport를 함께 소유합니다.
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export function App () {
return (
< view style = {{ width: "240px" }}>
< SelectRoot defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일을 선택하세요" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ view >
);
}
SelectRoot는 value/defaultValue와 open/defaultOpen을 관리합니다. multiple일 때는 항목을 탭해도 목록을 유지하고 선택을 toggle합니다.
SelectTrigger는 선택 값 또는 placeholder를 표시하고 탭으로 목록을 열고 닫습니다. SelectRoot의 문자열 label 또는 accessibility-label은 trigger의 기본 접근성 이름으로 사용됩니다.
SelectContent는 선택된 항목이 보이도록 목록을 스크롤하고, trigger 너비·화면 여유 공간·safe area에 맞춰 목록을 배치합니다.
SelectGroup은 관련 항목을 묶습니다. 여러 그룹 사이에는 구분선이 표시되고 label은 그룹 제목을 만듭니다.
SelectItem은 label, 선택적 description, prefix icon과 기본 checkmark indicator를 조합합니다. selectedIndicator로 checkmark를 교체할 수 있습니다.
낮은 수준의 조합이 필요하면 Select namespace의 Root, Trigger, Value, Placeholder, PrefixIcon, SuffixIcon, Content, ScrollArea, Group, GroupLabel, Item, ItemPrefixIcon, ItemBody, ItemLabel, ItemDescription, ItemIndicator를 사용하세요. 같은 API는 SelectRoot, SelectTrigger처럼 Select 접두어가 붙은 flat export로도 제공합니다.
label?React.ReactNode
labelWeight?"medium" | "bold" | undefined
indicator?React.ReactNode
showRequiredIndicator?boolean | undefined
description?React.ReactNode
errorMessage?React.ReactNode
fieldRef?React.Ref < NodesRef > | undefined
accessibility-label?string | undefined
value?string[] | undefined
defaultValue?string[] | undefined
onValueChange?(( value : string []) => void ) | undefined
multiple?boolean | undefined
open?boolean | undefined
defaultOpen?boolean | undefined
onOpenChange?(( open : boolean , details : SelectOpenChangeDetails ) => void ) | undefined
disabled?boolean | undefined
readOnly?boolean | undefined
invalid?boolean | undefined
required?boolean | undefined
placement?Placement | undefined
gutter?number | undefined
overflowPadding?number | undefined
formatValue?(( items : SelectSelectedItem []) => React.ReactNode) | undefined
size?"medium" | "large" | "responsive" | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
placeholder?React.ReactNode
prefixIcon?React.ReactElement < LynxIconElementProps, string | React.JSXElementConstructor < any >> | undefined
suffixIcon?React.ReactElement < LynxIconElementProps, string | React.JSXElementConstructor < any >> | undefined
style?CSSProperties | undefined
className?string | undefined
bindtap?EventHandler < BaseTouchEvent < Target >> | undefined
main-thread:bindtap?EventHandler < BaseTouchEvent < Element >> | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
label?React.ReactNode
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
labelReact.ReactNode
description?React.ReactNode
selectedIndicator?React.ReactNode
prefixIcon?React.ReactElement < LynxIconElementProps, string | React.JSXElementConstructor < any >> | undefined
disabled?boolean | undefined
readOnly?boolean | undefined
style?CSSProperties | undefined
className?string | undefined
bindtap?EventHandler < BaseTouchEvent < Target >> | undefined
main-thread:bindtap?EventHandler < BaseTouchEvent < Element >> | undefined
valuestring
textValue?string | undefined
size로 trigger와 목록의 크기를 정합니다. 기본값은 large이고, responsive는 native runtime의 화면 폭이 1280 이상이면 medium, 그 외에는 large로 해석합니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" gap = "x4" >
< Box width = "240px" >
< SelectRoot size = "large" defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일 (large)" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
< Box width = "240px" >
< SelectRoot size = "medium" defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일 (medium)" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
SelectGroup으로 옵션을 묶고 label로 그룹 제목을 표시합니다. 두 번째 그룹부터는 그룹 사이에 구분선이 추가됩니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "240px" >
< SelectRoot defaultValue = {[ "seoul" ]}>
< SelectTrigger accessibility-label = "지역" placeholder = "지역 선택" />
< SelectContent >
< SelectGroup label = "아시아" >
< SelectItem value = "seoul" label = "서울" />
< SelectItem value = "tokyo" label = "도쿄" />
< SelectItem value = "singapore" label = "싱가포르" />
< SelectItem value = "dubai" label = "두바이" />
</ SelectGroup >
< SelectGroup label = "유럽" >
< SelectItem value = "london" label = "런던" />
< SelectItem value = "paris" label = "파리" />
< SelectItem value = "berlin" label = "베를린" />
</ SelectGroup >
< SelectGroup label = "아메리카" >
< SelectItem value = "new-york" label = "뉴욕" />
< SelectItem value = "sao-paulo" label = "상파울루" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
multiple을 지정하면 선택한 옵션을 다시 탭해 해제할 수 있습니다. 다중 선택 중에는 목록이 닫히지 않으며 trigger에는 기본적으로 선택된 textValue가 ", "로 연결되어 표시됩니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "240px" >
< SelectRoot multiple defaultValue = {[ "apple" , "cherry" ]}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
< SelectItem value = "grape" label = "포도" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
SelectItem의 description으로 옵션에 부가 설명을 추가합니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "280px" >
< SelectRoot defaultValue = {[ "standard" ]}>
< SelectTrigger accessibility-label = "배송 방법" placeholder = "배송 방법 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "standard" label = "일반 배송" description = "3-5일 소요" />
< SelectItem value = "express" label = "빠른 배송" description = "1-2일 소요" />
< SelectItem value = "same-day" label = "당일 배송" description = "오늘 도착" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
SelectItem의 prefixIcon으로 옵션 앞에 아이콘을 표시합니다. 단일 옵션이 선택되면 해당 아이콘이 trigger prefix에 표시됩니다. 선택이 없거나 여러 옵션이 선택된 경우에는 SelectTrigger의 prefixIcon fallback을 사용합니다.
import "./styles" ;
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, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "280px" >
< SelectRoot >
< SelectTrigger
accessibility-label = "공유 대상"
placeholder = "공유 대상"
prefixIcon = {< IconPerson2Line />}
/>
< SelectContent >
< SelectGroup label = "그룹" >
< SelectItem value = "public" label = "전체 공개" prefixIcon = {< IconGlobeLine />} />
< SelectItem value = "followers" label = "팔로워만" prefixIcon = {< IconLockLine />} />
< SelectItem value = "private" label = "나만" prefixIcon = {< IconPersonLine />} />
</ SelectGroup >
< SelectGroup label = "사람" >
< SelectItem value = "kim" label = "김하늘" />
< SelectItem value = "lee" label = "이하늘" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
label에는 복합 JSX를 전달할 수 있습니다. 복합 label을 사용할 때는 trigger에 표시할 문자열과 옵션 접근성 이름을 위해 textValue를 함께 지정하세요.
import "./styles" ;
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 { Badge, Box, HStack, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "280px" >
< SelectRoot defaultValue = {[ "metro" ]}>
< SelectTrigger accessibility-label = "이동 수단" placeholder = "이동 수단 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "bike" label = "자전거" prefixIcon = {< IconFigureBikeLine />} />
< SelectItem
value = "metro"
textValue = "지하철"
prefixIcon = {< IconMetroFrontsideLine />}
label = {
< HStack align = "center" gap = "x1_5" >
< text >지하철</ text >
< Badge variant = "weak" tone = "informative" >
가장 빠름
</ Badge >
</ HStack >
}
/>
< SelectItem
value = "car"
textValue = "자동차"
disabled
prefixIcon = {< IconCarLine />}
label = {
< HStack align = "center" gap = "x1_5" >
< text >자동차</ text >
< Badge variant = "weak" tone = "warning" >
고객지원에 문의
</ Badge >
</ HStack >
}
/>
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
SelectItem의 disabled는 특정 옵션을, SelectRoot의 disabled는 Select 전체를 비활성화합니다. readOnly도 목록을 열거나 선택을 바꾸지 않습니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" gap = "x4" >
< Box width = "240px" >
< SelectRoot defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" disabled />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
< Box width = "240px" >
< SelectRoot disabled defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "비활성 과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
목록은 상하 8px 패딩을 포함한 내용 높이만큼 표시하며, 별도의 최소 높이는 없습니다. 최대 높이는 480px이고 trigger 주위의 남은 공간과 safe area에 맞춰 제한합니다. 내용이 이 높이를 넘을 때만 스크롤됩니다.
열릴 때 선택된 옵션이 화면 밖에 있으면 보이는 데 필요한 만큼만 스크롤합니다. 이미 보이는 옵션을 선택하거나 목록이 닫히는 중에는 스크롤 위치를 바꾸지 않습니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } 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 () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "240px" >
< SelectRoot defaultValue = {[ "14:00" ]}>
< SelectTrigger accessibility-label = "예약 시간" placeholder = "시간 선택" />
< SelectContent >
< SelectGroup >
{timeSlots. map (( slot ) => (
< SelectItem key = {slot} value = {slot} label = {slot} />
))}
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
placement로 목록 위치를 정합니다. 기본값은 bottom이며, 공간이 부족하면 native positioner가 반대쪽으로 뒤집거나 경계 안으로 이동합니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< Box width = "240px" >
< SelectRoot placement = "top" defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
value와 onValueChange로 선택 값을 제어할 수 있습니다.
import "./styles" ;
import { useState } from "@lynx-js/react" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
const [ value , setValue ] = useState < string []>([ "apple" ]);
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" gap = "x2" >
< Box width = "240px" >
< SelectRoot value = {value} onValueChange = {setValue}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
< text className = "select-preview__status" >
선택된 값: {value. length > 0 ? value. join ( ", " ) : "없음" }
</ text >
</ VStack >
</ view >
);
}
open과 onOpenChange로 목록 상태를 제어합니다. 초기 상태만 지정하려면 defaultOpen을 사용하세요.
import "./styles" ;
import { useState } from "@lynx-js/react" ;
import { ActionButton, Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
const [ open , setOpen ] = useState ( false );
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" gap = "x2" >
< Box width = "240px" >
< SelectRoot open = {open} onOpenChange = {setOpen} defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
< ActionButton variant = "neutralWeak" disabled = {open} bindtap = {() => setOpen ( true )}>
목록 열기
</ ActionButton >
< text className = "select-preview__status" >목록 상태: {open ? "열림" : "닫힘" }</ text >
</ VStack >
</ view >
);
}
onOpenChange의 두 번째 인자는 상태를 바꾼 이유를 제공합니다. 열림 이유는 trigger이고, 닫힘 이유는 trigger, itemSelect, interactOutside, dismiss입니다.
import "./styles" ;
import { useState } from "@lynx-js/react" ;
import { Box, HStack, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
const [ open , setOpen ] = useState ( false );
const [ openReason , setOpenReason ] = useState < string | null >( null );
const [ closeReason , setCloseReason ] = useState < string | null >( null );
function handleOpenChange ( nextOpen : boolean , details : { reason : string }) {
"background only" ;
setOpen (nextOpen);
(nextOpen ? setOpenReason : setCloseReason)(details.reason);
}
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" gap = "x4" >
< Box width = "240px" >
< SelectRoot open = {open} onOpenChange = {handleOpenChange} defaultValue = {[ "apple" ]}>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
< HStack gap = "x4" >
< text className = "select-preview__status" >마지막 열림 이유: {openReason ?? "-" }</ text >
< text className = "select-preview__status" >마지막 닫힘 이유: {closeReason ?? "-" }</ text >
</ HStack >
</ VStack >
</ view >
);
}
formatValue로 trigger에 표시할 선택 값을 바꿉니다. callback은 value, label, textValue, prefixIcon을 담은 선택 item 배열을 받습니다.
import "./styles" ;
import { Box, HStack, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
const listFormat = new Intl. ListFormat ( "ko" , { type: "conjunction" });
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" >
< HStack width = "full" gap = "x4" >
< Box style = {{ flex: 1 }}>
< SelectRoot
multiple
defaultValue = {[ "apple" , "banana" ]}
formatValue = {( items ) => listFormat. format (items. map (( item ) => item.textValue))}
>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
< Box style = {{ flex: 1 }}>
< SelectRoot
multiple
defaultValue = {[ "apple" , "banana" , "cherry" ]}
formatValue = {([ first , ... rest ]) =>
rest. length > 0 ? `${ first ?. textValue ?? ""} 외 ${ rest . length }개` : first?.textValue
}
>
< SelectTrigger accessibility-label = "과일" placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ HStack >
</ VStack >
</ view >
);
}
SelectRoot는 기존 Lynx Field를 조합하므로 label, description, errorMessage, invalid, required와 showRequiredIndicator를 지원합니다. 오류 메시지가 있으면 description 대신 오류 메시지를 표시합니다.
import "./styles" ;
import { Box, VStack, useSeedClassName } from "@seed-design/lynx-react" ;
import {
SelectContent,
SelectGroup,
SelectItem,
SelectRoot,
SelectTrigger,
} from "@/components/ui/select" ;
export default function Example () {
const seedClassName = useSeedClassName ({ colorMode: "system" });
return (
< view className = { `${ seedClassName } docs-lynx-select-root` }>
< VStack className = "select-preview" gap = "x6" >
< Box width = "240px" >
< SelectRoot
label = "과일"
description = "가장 좋아하는 과일을 선택하세요."
defaultValue = {[ "apple" ]}
>
< SelectTrigger placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
< Box width = "240px" >
< SelectRoot label = "과일" labelWeight = "bold" invalid errorMessage = "과일을 선택해주세요." >
< SelectTrigger placeholder = "과일 선택" />
< SelectContent >
< SelectGroup >
< SelectItem value = "apple" label = "사과" />
< SelectItem value = "banana" label = "바나나" />
< SelectItem value = "cherry" label = "체리" />
</ SelectGroup >
</ SelectContent >
</ SelectRoot >
</ Box >
</ VStack >
</ view >
);
}
Lynx에는 HTML <form>과 native <select> 제출 모델이 없습니다. 선택 값은 onValueChange로 앱 state에 저장하고, 제출 action의 payload에 그 값을 넣으세요.
현재 Lynx React 런타임과 이 예제 경로에는 React Hook Form 통합·dependency가 없습니다. value/onValueChange와 invalid/errorMessage를 앱 상태 및 native request action에 직접 연결하세요.
Lynx Select는 native overlay 위에 렌더링됩니다. HTML DOM의 focus와 form 모델 대신 native tap, overlay dismissal, accessibility-* 속성과 앱 상태를 사용합니다.
항목 React Lynx Trigger 조합 asChild와 DOM button 조합native SelectTrigger를 사용 위치와 긴 목록 DOM positioner와 scroll area SelectContent가 native overlay, 위치 계산, scroll viewport를 함께 소유열림 이유 keyboard/focus를 포함한 DOM 상호작용 trigger, itemSelect, interactOutside, dismiss키보드 focus, typeahead, Escape로 닫기 현재 Lynx Select에서 지원하지 않음 접근성 DOM ARIA id 연결 SelectRoot 또는 SelectTrigger의 accessibility-label과 item의 native 접근성 속성 사용반응형 size 웹 viewport 기반 native runtime 폭을 사용
기능 웹 대응 앱 대안 hidden native select, name, form 제출 <select>와 FormDataonValueChange로 앱 state를 갱신하고 제출 action payload에 그 값을 넣기browser validation required의 native form validation앱 제출 action에서 state를 검증하고 invalid/errorMessage를 제어하기 React Hook Form 예제 useController와 HTML form 통합현재 예제 경로에는 React Hook Form 통합·dependency가 없으므로 value/onValueChange 및 앱 상태·요청 action으로 연결하기 DOM focus, typeahead, keyboard navigation focus/keyboard API native tap과 host의 접근성 탐색 흐름 사용 DOM ARIA id aria-describedby, aria-controls각 trigger와 복합 item에 필요한 accessibility-* 속성을 직접 지정