Select
Select는 사용자가 여러 옵션 중에서 하나를 선택할 수 있는 드롭다운 컴포넌트입니다. 폼 입력, 설정 선택, 필터링 등에 사용되며, 키보드 내비게이션과 접근성을 완벽하게 지원합니다.import { Box, Select } from '@vapor-ui/core';
export default function DefaultSelect() {
return (
<Select.Root placeholder="폰트를 선택하세요">
<Box render={<Select.Trigger />} width="400px">
<Select.Value />
<Select.TriggerIcon />
</Box>
<Select.Content>
<Select.Group>
<Select.GroupLabel>폰트</Select.GroupLabel>
<Select.Item value="sans-serif">
Sans-serif
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="serif">
Serif
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="mono">
Monospace
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="cursive">
Cursive
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
);
}
Property
Size
Select의 크기는 sm, md, lg, xl 로 제공합니다.
import type { SelectRootProps } from '@vapor-ui/core';
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: SelectRootProps) => {
return (
<Select.Root {...props}>
<Select.Trigger>
<Select.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content>
<Select.Item value="option1">
옵션 1
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="option2">
옵션 2
<Select.ItemIndicator />
</Select.Item>
</Select.Content>
</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.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content positionerProps={{ side: 'top' }}>
<Select.Item value="option1">
옵션 1
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="option2">
옵션 2
<Select.ItemIndicator />
</Select.Item>
</Select.Content>
</Select.Root>
<Select.Root placeholder="Right">
<Select.Trigger>
<Select.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content positionerProps={{ side: 'right' }}>
<Select.Item value="option1">
옵션 1
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="option2">
옵션 2
<Select.ItemIndicator />
</Select.Item>
</Select.Content>
</Select.Root>
<Select.Root placeholder="Bottom">
<Select.Trigger>
<Select.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content positionerProps={{ side: 'bottom' }}>
<Select.Item value="option1">
옵션 1
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="option2">
옵션 2
<Select.ItemIndicator />
</Select.Item>
</Select.Content>
</Select.Root>
<Select.Root placeholder="Left">
<Select.Trigger>
<Select.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content positionerProps={{ side: 'left' }}>
<Select.Item value="option1">
옵션 1
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="option2">
옵션 2
<Select.ItemIndicator />
</Select.Item>
</Select.Content>
</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.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupLabel>폰트</Select.GroupLabel>
<Select.Item value="sans">
Sans-serif
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="serif">
Serif
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="mono">
Monospace
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="cursive">
Cursive
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
<Text typography="body2" foreground="secondary-200">
선택된 값: <code className="bg-gray-100 px-1 rounded">{value || '없음'}</code>
</Text>
<HStack gap="$100">
<Button color="primary" onClick={() => setValue('serif')}>
Serif 선택
</Button>
<Button onClick={() => setValue('')} color="secondary">
선택 해제
</Button>
</HStack>
</div>
);
}
States
Select의 다양한 상태(비활성화, 읽기 전용, 오류)를 설정할 수 있습니다.
import type { SelectRootProps } from '@vapor-ui/core';
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: SelectRootProps) => {
return (
<Select.Root {...props}>
<Select.Trigger>
<Select.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content>
<Select.Item value="option1">
옵션 1
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="option2">
옵션 2
<Select.ItemIndicator />
</Select.Item>
</Select.Content>
</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.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupLabel>폰트</Select.GroupLabel>
{fonts.map((font) => (
<Select.Item key={font.value} value={font.value}>
{font.label}
<Select.ItemIndicator />
</Select.Item>
))}
</Select.Group>
</Select.Content>
</Select.Root>
</VStack>
<VStack>
<h4 className="text-sm font-medium mb-2">객체 형태의 아이템</h4>
<Select.Root placeholder="언어 선택" items={languages}>
<Select.Trigger>
<Select.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupLabel>프로그래밍 언어</Select.GroupLabel>
{Object.entries(languages).map(([value, label]) => (
<Select.Item key={value} value={value}>
{label}
<Select.ItemIndicator />
</Select.Item>
))}
</Select.Group>
</Select.Content>
</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.Value />
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content>
<Select.Group>
<Select.GroupLabel>프론트엔드</Select.GroupLabel>
<Select.Item value="react">
React
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="vue">
Vue
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="angular">
Angular
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>백엔드</Select.GroupLabel>
<Select.Item value="nodejs">
Node.js
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="python">
Python
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="java">
Java
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>데이터베이스</Select.GroupLabel>
<Select.Item value="mysql">
MySQL
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="postgresql">
PostgreSQL
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="mongodb">
MongoDB
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
</Select.Content>
</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.Value>{renderValue}</Select.Value>
<Select.TriggerIcon />
</Select.Trigger>
<Select.Content>
{Object.entries(fonts).map(([value, label]) => (
<Select.Item key={value} value={value}>
<span style={{ fontFamily: value }}>{label}</span>
<Select.ItemIndicator />
</Select.Item>
))}
</Select.Content>
</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.Value
선택된 값을 표시하는 컴포넌트입니다. 함수형 children을 통해 커스텀 값 표시가 가능합니다.
Prop | Default | Type |
---|---|---|
children? | null | React.ReactNode(value: unknown) => React.ReactNode |
render? | span | React.ReactElement |
className? | null | string |
Select.Placeholder
값이 선택되지 않았을 때 표시되는 플레이스홀더 컴포넌트입니다.
Prop | Default | Type |
---|---|---|
render? | span | React.ReactElement |
className? | null | string |
Select.TriggerIcon
트리거 버튼의 드롭다운 아이콘을 표시하는 컴포넌트입니다.
Prop | Default | Type |
---|---|---|
children? | ChevronDownOutlineIcon | React.ReactNode |
render? | div | React.ReactElement |
className? | null | string |
Select.Portal
Select 드롭다운을 DOM의 다른 위치에 렌더링하는 포털 컴포넌트입니다.
Prop | Default | Type |
---|---|---|
container? | document.body | HTMLElement() => HTMLElement |
Select.Positioner
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.Popup
Select의 실제 드롭다운 팝업 영역입니다.
Prop | Default | Type |
---|---|---|
render? | div | React.ReactElement |
className? | null | string |
Select.Content
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.ItemIndicator
선택된 아이템에 표시되는 인디케이터 아이콘 컴포넌트입니다.
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 |