Select
Select는 사용자가 여러 옵션 중에서 하나를 선택할 수 있는 드롭다운 컴포넌트입니다. 폼 입력, 설정 선택, 필터링 등에 사용되며, 키보드 내비게이션과 접근성을 완벽하게 지원합니다.import { Select } from '@vapor-ui/core';
export default function DefaultSelect() {
return (
<Select.Root placeholder="폰트를 선택하세요">
<Select.Trigger width="400px" />
<Select.Popup>
<Select.Group>
<Select.GroupLabel>폰트</Select.GroupLabel>
<Select.Item value="sans-serif">Sans-serif</Select.Item>
<Select.Item value="serif">Serif</Select.Item>
<Select.Item value="mono">Monospace</Select.Item>
<Select.Item value="cursive">Cursive</Select.Item>
</Select.Group>
</Select.Popup>
</Select.Root>
);
}Property
Size
Select의 크기는 sm, md, lg, xl 로 제공합니다.
import { Flex, Select } from '@vapor-ui/core';
export default function SelectSize() {
return (
<Flex maxWidth="800px" width="100%" gap="$250">
<SelectTemplate placeholder="Small" size="sm" />
<SelectTemplate placeholder="Medium" size="md" />
<SelectTemplate placeholder="Large" size="lg" />
<SelectTemplate placeholder="Extra Large" size="xl" />
</Flex>
);
}
const SelectTemplate = (props: Select.Root.Props) => {
return (
<Select.Root {...props}>
<Select.Trigger />
<Select.Popup>
<Select.Item value="option1">옵션 1</Select.Item>
<Select.Item value="option2">옵션 2</Select.Item>
</Select.Popup>
</Select.Root>
);
};Positioning
Select 드롭다운이 나타날 위치를 설정할 수 있습니다. 기본값은 'bottom'입니다.
import { HStack, Select } from '@vapor-ui/core';
export default function SelectPositioning() {
return (
<HStack maxWidth="800px" width="100%" gap="$250">
<Select.Root placeholder="Top">
<Select.Trigger />
<Select.Popup positionerElement={<Select.PositionerPrimitive side="top" />}>
<Select.Item value="option1">옵션 1</Select.Item>
<Select.Item value="option2">옵션 2</Select.Item>
</Select.Popup>
</Select.Root>
<Select.Root placeholder="Right">
<Select.Trigger />
<Select.Popup positionerElement={<Select.PositionerPrimitive side="right" />}>
<Select.Item value="option1">옵션 1</Select.Item>
<Select.Item value="option2">옵션 2</Select.Item>
</Select.Popup>
</Select.Root>
<Select.Root placeholder="Bottom">
<Select.Trigger />
<Select.Popup positionerElement={<Select.PositionerPrimitive side="bottom" />}>
<Select.Item value="option1">옵션 1</Select.Item>
<Select.Item value="option2">옵션 2</Select.Item>
</Select.Popup>
</Select.Root>
<Select.Root placeholder="Left">
<Select.Trigger />
<Select.Popup positionerElement={<Select.PositionerPrimitive side="left" />}>
<Select.Item value="option1">옵션 1</Select.Item>
<Select.Item value="option2">옵션 2</Select.Item>
</Select.Popup>
</Select.Root>
</HStack>
);
}Controlled State
Select의 선택 상태를 외부에서 제어할 수 있습니다.
'use client';
import { useState } from 'react';
import { Button, HStack, Select, Text } from '@vapor-ui/core';
export default function SelectControlled() {
const [value, setValue] = useState<string>('');
const handleValueChange = (newValue: unknown) => {
setValue(newValue as string);
};
return (
<div className="space-y-4">
<Select.Root placeholder="폰트 선택" value={value} onValueChange={handleValueChange}>
<Select.Trigger />
<Select.Popup>
<Select.Group>
<Select.GroupLabel>폰트</Select.GroupLabel>
<Select.Item value="sans">Sans-serif</Select.Item>
<Select.Item value="serif">Serif</Select.Item>
<Select.Item value="mono">Monospace</Select.Item>
<Select.Item value="cursive">Cursive</Select.Item>
</Select.Group>
</Select.Popup>
</Select.Root>
<Text typography="body2" foreground="secondary-200">
선택된 값: <code className="bg-gray-100 px-1 rounded">{value || '없음'}</code>
</Text>
<HStack gap="$100">
<Button colorPalette="primary" onClick={() => setValue('serif')}>
Serif 선택
</Button>
<Button onClick={() => setValue('')} colorPalette="secondary">
선택 해제
</Button>
</HStack>
</div>
);
}States
Select의 다양한 상태(비활성화, 읽기 전용, 오류)를 설정할 수 있습니다.
import { Select, VStack } from '@vapor-ui/core';
export default function SelectStates() {
return (
<VStack gap="$200" width="400px" className="flex-wrap">
<SelectTemplate placeholder="기본 상태" />
<SelectTemplate placeholder="비활성화" disabled />
<SelectTemplate placeholder="읽기 전용" readOnly />
<SelectTemplate placeholder="오류 상태" invalid />
</VStack>
);
}
const SelectTemplate = (props: Select.Root.Props) => {
return (
<Select.Root {...props}>
<Select.Trigger />
<Select.Popup>
<Select.Item value="option1">옵션 1</Select.Item>
<Select.Item value="option2">옵션 2</Select.Item>
</Select.Popup>
</Select.Root>
);
};Examples
Items Configuration
Select는 배열 형태와 객체 형태의 아이템 데이터를 모두 지원합니다. items prop을 사용하면 자동으로 값이 포맷팅됩니다.
import { Select, VStack } from '@vapor-ui/core';
const fonts = [
{ label: 'Sans-serif', value: 'sans' },
{ label: 'Serif', value: 'serif' },
{ label: 'Monospace', value: 'mono' },
{ label: 'Cursive', value: 'cursive' },
];
const languages = {
javascript: 'JavaScript',
typescript: 'TypeScript',
python: 'Python',
java: 'Java',
go: 'Go',
};
export default function SelectItems() {
return (
<VStack gap="$300" width="400px">
<VStack>
<h4 className="text-sm font-medium mb-2">배열 형태의 아이템</h4>
<Select.Root placeholder="폰트 선택" items={fonts}>
<Select.Trigger />
<Select.Popup>
<Select.Group>
<Select.GroupLabel>폰트</Select.GroupLabel>
{fonts.map((font) => (
<Select.Item key={font.value} value={font.value}>
{font.label}
</Select.Item>
))}
</Select.Group>
</Select.Popup>
</Select.Root>
</VStack>
<VStack>
<h4 className="text-sm font-medium mb-2">객체 형태의 아이템</h4>
<Select.Root placeholder="언어 선택" items={languages}>
<Select.Trigger />
<Select.Popup>
<Select.Group>
<Select.GroupLabel>프로그래밍 언어</Select.GroupLabel>
{Object.entries(languages).map(([value, label]) => (
<Select.Item key={value} value={value}>
{label}
</Select.Item>
))}
</Select.Group>
</Select.Popup>
</Select.Root>
</VStack>
</VStack>
);
}Grouping Options
관련된 옵션들을 그룹으로 묶어 구조화할 수 있습니다. Group과 GroupLabel, Separator를 사용하여 명확한 구조를 만들 수 있습니다.
import { Select, VStack } from '@vapor-ui/core';
export default function SelectGrouping() {
return (
<VStack width="400px">
<Select.Root placeholder="개발 도구 선택">
<Select.Trigger />
<Select.Popup>
<Select.Group>
<Select.GroupLabel>프론트엔드</Select.GroupLabel>
<Select.Item value="react">React</Select.Item>
<Select.Item value="vue">Vue</Select.Item>
<Select.Item value="angular">Angular</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>백엔드</Select.GroupLabel>
<Select.Item value="nodejs">Node.js</Select.Item>
<Select.Item value="python">Python</Select.Item>
<Select.Item value="java">Java</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>데이터베이스</Select.GroupLabel>
<Select.Item value="mysql">MySQL</Select.Item>
<Select.Item value="postgresql">PostgreSQL</Select.Item>
<Select.Item value="mongodb">MongoDB</Select.Item>
</Select.Group>
</Select.Popup>
</Select.Root>
</VStack>
);
}Custom Value Display
Select.Value에 함수형 children을 제공하여 선택된 값을 커스터마이징할 수 있습니다.
import { Box, Select } from '@vapor-ui/core';
const fonts = {
sans: 'Sans-serif',
serif: 'Serif',
mono: 'Monospace',
cursive: 'Cursive',
};
const renderValue = (value: string) => {
if (!value) return '선택된 폰트 없음';
return <span style={{ fontFamily: value }}>{fonts[value as keyof typeof fonts]}</span>;
};
export default function SelectCustomValue() {
return (
<Box width="400px">
<Select.Root placeholder="폰트 선택" items={fonts}>
<h4 className="text-sm font-medium mb-2">커스텀 값 표시</h4>
<Select.Trigger>
<Select.ValuePrimitive>{renderValue}</Select.ValuePrimitive>
<Select.TriggerIconPrimitive />
</Select.Trigger>
<Select.Popup>
{Object.entries(fonts).map(([value, label]) => (
<Select.Item key={value} value={value}>
<span style={{ fontFamily: value }}>{label}</span>
</Select.Item>
))}
</Select.Popup>
</Select.Root>
</Box>
);
}Props Table
Select.Root
Select의 루트 컨테이너로, 전체 Select 컴포넌트의 상태와 동작을 관리합니다.
| Prop | Default | Type |
|---|---|---|
items? | null | Array<{label: string, value: string}>Record<string, string> |
placeholder? | null | React.ReactNode |
size? | 'md' | 'sm''md''lg''xl' |
invalid? | false | boolean |
value? | null | unknown |
defaultValue? | null | unknown |
onValueChange? | null | (value: unknown, event?: Event) => void |
disabled? | false | boolean |
readOnly? | false | boolean |
open? | false | boolean |
defaultOpen? | false | boolean |
onOpenChange? | null | (open: boolean, event?: Event) => void |
Select.Trigger
Select 드롭다운을 여는 트리거 요소입니다.
| Prop | Default | Type |
|---|---|---|
render? | button | React.ReactElement |
className? | null | string |
nativeButton? | true | boolean |
Select.ValuePrimitive
선택된 값을 표시하는 컴포넌트입니다. 함수형 children을 통해 커스텀 값 표시가 가능합니다.
| Prop | Default | Type |
|---|---|---|
children? | null | React.ReactNode(value: unknown) => React.ReactNode |
render? | span | React.ReactElement |
className? | null | string |
Select.PlaceholderPrimitive
값이 선택되지 않았을 때 표시되는 플레이스홀더 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
render? | span | React.ReactElement |
className? | null | string |
Select.TriggerIconPrimitive
트리거 버튼의 드롭다운 아이콘을 표시하는 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
children? | ChevronDownOutlineIcon | React.ReactNode |
render? | div | React.ReactElement |
className? | null | string |
Select.PortalPrimitive
Select 드롭다운을 DOM의 다른 위치에 렌더링하는 포털 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
container? | document.body | HTMLElement() => HTMLElement |
Select.PositionerPrimitive
Select 드롭다운의 위치를 설정하는 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
side? | 'bottom' | 'top''right''bottom''left' |
align? | 'start' | 'start''center''end' |
sideOffset? | 4 | number |
alignOffset? | 0 | number |
alignItemWithTrigger? | false | boolean |
className? | null | string |
Select.PopupPrimitive
Select의 실제 드롭다운 팝업 영역입니다.
| Prop | Default | Type |
|---|---|---|
render? | div | React.ReactElement |
className? | null | string |
Select.Popup
Select의 드롭다운 콘텐츠를 담는 컨테이너입니다. Portal과 Positioner를 조합하여 구성됩니다.
| Prop | Default | Type |
|---|---|---|
portalProps? | null | SelectPortalProps |
positionerProps? | null | SelectPositionerProps |
render? | div | React.ReactElement |
className? | null | string |
Select.Item
개별 선택 옵션을 나타내는 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
value? | null | unknown |
render? | div | React.ReactElement |
className? | null | string |
children? | null | React.ReactNode |
Select.ItemIndicatorPrimitive
선택된 아이템에 표시되는 인디케이터 아이콘 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
children? | ConfirmOutlineIcon | React.ReactNode |
render? | span | React.ReactElement |
className? | null | string |
Select.Group
관련된 아이템들을 그룹화하는 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
render? | div | React.ReactElement |
className? | null | string |
children? | null | React.ReactNode |
Select.GroupLabel
그룹의 라벨을 표시하는 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
render? | div | React.ReactElement |
className? | null | string |
children? | null | React.ReactNode |
Select.Separator
그룹 간의 구분선을 표시하는 컴포넌트입니다.
| Prop | Default | Type |
|---|---|---|
render? | div | React.ReactElement |
className? | null | string |