MultiSelect
MultiSelect는 사용자가 여러 옵션 중에서 다중 선택할 수 있는 드롭다운 컴포넌트입니다. 선택된 값들은 배지 형태로 표시되며, 태그 선택, 필터링, 카테고리 선택 등에 사용됩니다.import { MultiSelect } from '@vapor-ui/core';
const fonts = [
{ label: 'Sans-serif', value: 'sans' },
{ label: 'Serif', value: 'serif' },
{ label: 'Monospace', value: 'mono' },
{ label: 'Cursive', value: 'cursive' },
];
export default function DefaultMultiSelect() {
return (
<MultiSelect.Root items={fonts} placeholder="폰트를 선택하세요">
<MultiSelect.Trigger />
<MultiSelect.Popup>
{fonts.map((font) => (
<MultiSelect.Item key={font.value} value={font.value}>
{font.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
);
}Property
Size
MultiSelect의 크기를 설정합니다.
import { Flex, MultiSelect } from '@vapor-ui/core';
const options = [
{ label: '옵션 1', value: 'option1' },
{ label: '옵션 2', value: 'option2' },
{ label: '옵션 3', value: 'option3' },
];
export default function MultiSelectSize() {
return (
<Flex gap="$200" className="flex-wrap" width="400px">
<MultiSelect.Root placeholder="Small" size="sm" items={options}>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{options.map((option) => (
<MultiSelect.Item key={option.value} value={option.value}>
{option.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
<MultiSelect.Root placeholder="Medium" size="md" items={options}>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{options.map((option) => (
<MultiSelect.Item key={option.value} value={option.value}>
{option.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
<MultiSelect.Root placeholder="Large" size="lg" items={options}>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{options.map((option) => (
<MultiSelect.Item key={option.value} value={option.value}>
{option.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
<MultiSelect.Root placeholder="Extra Large" size="xl" items={options}>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{options.map((option) => (
<MultiSelect.Item key={option.value} value={option.value}>
{option.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
</Flex>
);
}Controlled State
MultiSelect의 선택 상태를 제어합니다.
'use client';
import { useState } from 'react';
import { Button, HStack, MultiSelect, Text, VStack } from '@vapor-ui/core';
const fonts = [
{ label: 'Sans-serif', value: 'sans' },
{ label: 'Serif', value: 'serif' },
{ label: 'Monospace', value: 'mono' },
{ label: 'Cursive', value: 'cursive' },
];
export default function MultiSelectControlled() {
const [value, setValue] = useState<string[]>([]);
const handleValueChange = (newValue: unknown) => {
setValue(newValue as string[]);
};
return (
<VStack gap="$200" width="400px">
<MultiSelect.Root
items={fonts}
value={value}
onValueChange={handleValueChange}
placeholder="폰트 선택"
>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{fonts.map((font) => (
<MultiSelect.Item key={font.value} value={font.value}>
{font.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
<Text typography="body2" foreground="secondary-200">
선택된 값:{' '}
<code className="bg-gray-100 px-1 rounded">
{value.length > 0 ? value.join(', ') : '없음'}
</code>
</Text>
<HStack gap="$100">
<Button colorPalette="primary" onClick={() => setValue(['serif', 'mono'])}>
Serif, Mono 선택
</Button>
<Button colorPalette="secondary" onClick={() => setValue([])}>
모두 해제
</Button>
</HStack>
</VStack>
);
}States
MultiSelect의 다양한 상태를 설정합니다.
import { MultiSelect, VStack } from '@vapor-ui/core';
const options = [
{ label: '옵션 1', value: 'option1' },
{ label: '옵션 2', value: 'option2' },
{ label: '옵션 3', value: 'option3' },
];
export default function MultiSelectStates() {
return (
<VStack gap="$200" width="400px">
<MultiSelectTemplate placeholder="기본 상태" />
<MultiSelectTemplate placeholder="비활성화" disabled />
<MultiSelectTemplate placeholder="읽기 전용" readOnly />
<MultiSelectTemplate placeholder="오류 상태" invalid />
</VStack>
);
}
export const MultiSelectTemplate = (props: MultiSelect.Root.Props<string>) => {
return (
<MultiSelect.Root {...props}>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{options.map((option) => (
<MultiSelect.Item key={option.value} value={option.value}>
{option.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
);
};Examples
Items Configuration
다양한 형태의 아이템 데이터를 사용합니다.
import { HStack, MultiSelect, Text, 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 MultiSelectItems() {
return (
<HStack gap="$500">
<VStack gap="$100" width="300px">
<MultiSelect.Root placeholder="폰트 선택" items={fonts}>
<Text typography="body2">배열 형태의 아이템</Text>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{fonts.map((font) => (
<MultiSelect.Item key={font.value} value={font.value}>
{font.label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
</VStack>
<VStack gap="$100" width="300px">
<MultiSelect.Root placeholder="언어 선택" items={languages}>
<Text typography="body2">객체 형태의 아이템</Text>
<MultiSelect.Trigger />
<MultiSelect.Popup>
{Object.entries(languages).map(([value, label]) => (
<MultiSelect.Item key={value} value={value}>
{label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
</VStack>
</HStack>
);
}Grouping Options
옵션을 그룹으로 묶어 구조화합니다.
import { Box, MultiSelect } from '@vapor-ui/core';
export default function MultiSelectGrouping() {
return (
<MultiSelect.Root placeholder="개발 기술 선택">
<Box render={<MultiSelect.Trigger />} width="400px" />
<MultiSelect.Popup>
<MultiSelect.Group>
<MultiSelect.GroupLabel>프론트엔드</MultiSelect.GroupLabel>
<MultiSelect.Item value="react">React</MultiSelect.Item>
<MultiSelect.Item value="vue">Vue</MultiSelect.Item>
<MultiSelect.Item value="angular">Angular</MultiSelect.Item>
<MultiSelect.Item value="svelte">Svelte</MultiSelect.Item>
</MultiSelect.Group>
<MultiSelect.Separator />
<MultiSelect.Group>
<MultiSelect.GroupLabel>백엔드</MultiSelect.GroupLabel>
<MultiSelect.Item value="nodejs">Node.js</MultiSelect.Item>
<MultiSelect.Item value="python">Python</MultiSelect.Item>
<MultiSelect.Item value="java">Java</MultiSelect.Item>
<MultiSelect.Item value="go">Go</MultiSelect.Item>
</MultiSelect.Group>
<MultiSelect.Separator />
<MultiSelect.Group>
<MultiSelect.GroupLabel>데이터베이스</MultiSelect.GroupLabel>
<MultiSelect.Item value="mysql">MySQL</MultiSelect.Item>
<MultiSelect.Item value="postgresql">PostgreSQL</MultiSelect.Item>
<MultiSelect.Item value="mongodb">MongoDB</MultiSelect.Item>
<MultiSelect.Item value="redis">Redis</MultiSelect.Item>
</MultiSelect.Group>
</MultiSelect.Popup>
</MultiSelect.Root>
);
}Custom Value Display
MultiSelect.Value에 함수형 children을 제공하여 선택된 값들의 표시 방법을 커스터마이징할 수 있습니다. 기본적으로는 배지 형태로 표시되지만, 문자열이나 커스텀 컴포넌트로 변경할 수 있습니다.
import { Badge, Flex, Grid, MultiSelect, Text, VStack } from '@vapor-ui/core';
const languages = {
javascript: 'JavaScript',
typescript: 'TypeScript',
python: 'Python',
java: 'Java',
go: 'Go',
rust: 'Rust',
};
const renderRestValue = (value: string[]) => {
if (!value.length) {
return <MultiSelect.PlaceholderPrimitive>언어 선택</MultiSelect.PlaceholderPrimitive>;
}
const displayValues = value.slice(0, 2);
const remainingCount = value.length - 2;
return (
<Flex gap="$050" className="flex-wrap">
{displayValues.map((val) => (
<Badge key={val} size="sm">
{languages[val as keyof typeof languages]}
</Badge>
))}
{remainingCount > 0 && (
<Badge size="sm" colorPalette="hint">
+{remainingCount} more
</Badge>
)}
</Flex>
);
};
const renderStringValue = (value: string[]) => {
if (!value.length) {
return <MultiSelect.PlaceholderPrimitive>언어 선택</MultiSelect.PlaceholderPrimitive>;
}
return value.map((v) => languages[v as keyof typeof languages]).join(', ');
};
export default function MultiSelectCustomValue() {
return (
<Grid.Root templateColumns="1fr 1fr" gap="$300">
<VStack gap="$100" width="250px">
<Text typography="body2">커스텀 값 표시 (최대 2개 + 더보기)</Text>
<MultiSelect.Root items={languages} placeholder="언어 선택">
<MultiSelect.Trigger>
<MultiSelect.ValuePrimitive>{renderRestValue}</MultiSelect.ValuePrimitive>
<MultiSelect.TriggerIconPrimitive />
</MultiSelect.Trigger>
<MultiSelect.Popup>
{Object.entries(languages).map(([value, label]) => (
<MultiSelect.Item key={value} value={value}>
{label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
</VStack>
<VStack gap="$100" width="250px">
<Text typography="body2">문자열 형태 표시</Text>
<MultiSelect.Root items={languages} placeholder="언어 선택">
<MultiSelect.Trigger>
<MultiSelect.ValuePrimitive>{renderStringValue}</MultiSelect.ValuePrimitive>
<MultiSelect.TriggerIconPrimitive />
</MultiSelect.Trigger>
<MultiSelect.Popup>
{Object.entries(languages).map(([value, label]) => (
<MultiSelect.Item key={value} value={value}>
{label}
</MultiSelect.Item>
))}
</MultiSelect.Popup>
</MultiSelect.Root>
</VStack>
</Grid.Root>
);
}Props Table
MultiSelect.Root
Loading component documentation...
MultiSelect.Trigger
Loading component documentation...
MultiSelect.TriggerPrimitive
Loading component documentation...
MultiSelect.TriggerIconPrimitive
Loading component documentation...
MultiSelect.ValuePrimitive
Loading component documentation...
MultiSelect.PlaceholderPrimitive
Loading component documentation...
MultiSelect.Popup
Loading component documentation...
MultiSelect.PortalPrimitive
Loading component documentation...
MultiSelect.PositionerPrimitive
Loading component documentation...
MultiSelect.PopupPrimitive
Loading component documentation...
MultiSelect.Item
Loading component documentation...
MultiSelect.ItemPrimitive
Loading component documentation...
MultiSelect.ItemIndicatorPrimitive
Loading component documentation...
MultiSelect.Separator
Loading component documentation...
MultiSelect.Group
Loading component documentation...
MultiSelect.GroupLabel
Loading component documentation...