ComboboxPopover
ComboboxPopover (콤보박스 팝오버)
ComboboxPopover 컴포넌트는 선택 가능한 옵션을 가진 콤보박스 드롭다운을 어떤 버튼 요소에든 추가할 수 있게 해 주는 컴포넌트예요. Select나 MultiSelect와 달리 input을 렌더링하지 않아요.
출처: 문서
본문
ComboboxPopover는 선택 가능한 옵션을 가진 콤보박스 드롭다운을 어떤 버튼 요소에든 추가할 수 있게 해 줘요. Select와 MultiSelect와 달리 input을 렌더링하지 않아요. 대신 ComboboxPopover.Target으로 직접 대상 요소(보통 Button)를 제공해요.
ComboboxPopover.Target 자식은 ref를 받는 단일 요소나 컴포넌트여야 해요. Fragment, 문자열, 기타 원시(primitive) 값은 지원되지 않아요.
import { useState } from 'react';
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string | null>(null);
return (
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value}
onChange={setValue}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select framework'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
Data 포맷
ComboboxPopover는 Select 컴포넌트와 같은 data 포맷을 지원해요. 문자열 배열, value와 label을 가진 객체, 또는 그룹 항목이에요.
Data 포맷들
ComboboxPopover의 data prop은 다음 포맷 중 하나로 데이터를 받아요.
원시 값(문자열, 숫자, boolean) 배열:
import { ComboboxPopover } from '@mantine/core';
function Demo() {
return <ComboboxPopover data={['React', 'Angular']} />;
}
value, label, 선택적 disabled 키를 가진 객체 배열:
import { ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={[
{ value: 'react', label: 'React' },
{ value: 'ng', label: 'Angular' },
]}
/>
);
}
원시 값(문자열, 숫자, boolean) 옵션을 가진 그룹 배열:
import { ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={[
{ group: 'Frontend', items: ['React', 'Angular'] },
{ group: 'Backend', items: ['Express', 'Django'] },
]}
/>
);
}
객체 옵션을 가진 그룹 배열:
import { ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={[
{ group: 'Frontend', items: [{ value: 'react', label: 'React' }, { value: 'ng', label: 'Angular' }] },
{ group: 'Backend', items: [{ value: 'express', label: 'Express' }, { value: 'django', label: 'Django' }] },
]}
/>
);
}
검색 가능 (Searchable)
searchable prop을 설정하면 입력하는 대로 옵션을 필터링하는 검색 input이 드롭다운 안에 활성화돼요. nothingFoundMessage로 검색 쿼리와 일치하는 옵션이 없을 때 메시지를 표시할 수 있어요.
import { useState } from 'react';
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string | null>(null);
return (
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value}
onChange={setValue}
searchable
nothingFoundMessage="Nothing found..."
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select framework'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
제어되는 검색 값 (Controlled search value)
searchValue와 onSearchChange prop으로 검색 input 값을 제어할 수 있어요.
import { useState } from 'react';
import { Button, ComboboxPopover, Text } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string | null>(null);
const [searchValue, setSearchValue] = useState('');
return (
<>
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value}
onChange={setValue}
searchable
searchValue={searchValue}
onSearchChange={setSearchValue}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select framework'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
<Text mt="md" size="sm">
Search value: <b>{searchValue || '(empty)'}</b>
</Text>
<Text size="sm">
Selected value: <b>{value || '(none)'}</b>
</Text>
</>
);
}
옵션 정렬 (Sort options)
searchable과 함께 filter prop을 사용해 커스텀 필터링과 정렬 함수를 제공할 수 있어요.
import { useState } from 'react';
import { Button, ComboboxItem, ComboboxPopover, OptionsFilter } from '@mantine/core';
const optionsFilter: OptionsFilter = ({ options, search }) => {
const filtered = (options as ComboboxItem[]).filter((option) =>
option.label.toLowerCase().trim().includes(search.toLowerCase().trim())
);
filtered.sort((a, b) => a.label.localeCompare(b.label));
return filtered;
};
function Demo() {
const [value, setValue] = useState<string | null>(null);
return (
<ComboboxPopover
data={['4 – React', '1 – Angular', '3 – Vue', '2 – Svelte']}
value={value}
onChange={setValue}
searchable
filter={optionsFilter}
nothingFoundMessage="Nothing found..."
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select framework'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
옵션 제한 (Limit options)
searchable과 함께 limit prop을 사용해 한 번에 표시되는 옵션 수를 제한할 수 있어요. 큰 데이터셋에서 성능을 개선하는 데 유용해요.
import { useState } from 'react';
import { Button, ComboboxPopover } from '@mantine/core';
const largeData = Array(1000)
.fill(0)
.map((_, index) => `Option ${index}`);
function Demo() {
const [value, setValue] = useState<string | null>(null);
return (
<ComboboxPopover
data={largeData}
value={value}
onChange={setValue}
searchable
limit={5}
nothingFoundMessage="Nothing found..."
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select option'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
다중 선택 (Multiple selection)
multiple prop을 설정하면 여러 값을 선택할 수 있어요. multiple이 설정되면 value 타입이 string | null에서 string[]으로 바뀌고, onChange 콜백이 선택된 값 배열을 받아요.
import { useState } from 'react';
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string[]>([]);
return (
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value}
onChange={setValue}
multiple
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>
{value.length > 0 ? value.join(', ') : 'Select frameworks'}
</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
체크 아이콘 (Check icon)
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
checkIconPosition="left"
data={['React', 'Angular', 'Svelte', 'Vue']}
dropdownOpened
defaultValue="React"
>
<ComboboxPopover.Target>
<Button variant="default" miw={200} mb={150}>Select framework</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
선택 해제 허용 (Allow deselect)
기본적으로 선택된 옵션은 다시 클릭하면 선택 해제할 수 있어요. 이 동작을 막으려면 allowDeselect={false}를 설정해요.
import { useState } from 'react';
import { Button, ComboboxPopover, Stack } from '@mantine/core';
function Demo() {
const [value1, setValue1] = useState<string | null>('React');
const [value2, setValue2] = useState<string | null>('React');
return (
<Stack>
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value1}
onChange={setValue1}
allowDeselect={false}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>
{value1 || 'Cannot deselect'}
</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value2}
onChange={setValue2}
allowDeselect
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>
{value2 || 'Can deselect (default)'}
</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
</Stack>
);
}
결과 없음 메시지 (Nothing found message)
nothingFoundMessage prop을 설정하면 사용 가능한 옵션이 없을 때 메시지를 표시해요.
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={[]}
nothingFoundMessage="No options available"
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>Open dropdown</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
비활성 옵션 (Disabled options)
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={[
{ value: 'react', label: 'React' },
{ value: 'ng', label: 'Angular' },
{ value: 'vue', label: 'Vue', disabled: true },
{ value: 'svelte', label: 'Svelte', disabled: true },
]}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>Select framework</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
그룹 (Groups)
import { useState } from 'react';
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string | null>(null);
return (
<ComboboxPopover
data={[
{ group: 'Frontend', items: ['React', 'Angular', 'Vue'] },
{ group: 'Backend', items: ['Node.js', 'Django', 'Rails'] },
]}
value={value}
onChange={setValue}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select technology'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
옵션 렌더링 (Render option)
renderOption prop으로 옵션 렌더링을 커스터마이즈할 수 있어요.
import { useState } from 'react';
import {
CheckIcon,
TextAlignCenterIcon,
TextAlignJustifyIcon,
TextAlignLeftIcon,
TextAlignRightIcon,
} from '@phosphor-icons/react';
import { Button, ComboboxPopover, ComboboxPopoverProps, Group } from '@mantine/core';
const iconProps = {
color: 'currentColor',
opacity: 0.6,
size: 18,
};
const icons: Record<string, React.ReactNode> = {
left: <TextAlignLeftIcon {...iconProps} />,
center: <TextAlignCenterIcon {...iconProps} />,
right: <TextAlignRightIcon {...iconProps} />,
justify: <TextAlignJustifyIcon {...iconProps} />,
};
const renderSelectOption: ComboboxPopoverProps['renderOption'] = ({ option, checked }) => (
<Group flex="1" gap="xs">
{icons[option.value]}
{option.label}
{checked && <CheckIcon style={{ marginInlineStart: 'auto' }} {...iconProps} />}
</Group>
);
function Demo() {
const [value, setValue] = useState<string | null>(null);
return (
<ComboboxPopover
data={[
{ value: 'left', label: 'Left' },
{ value: 'center', label: 'Center' },
{ value: 'right', label: 'Right' },
{ value: 'justify', label: 'Justify' },
]}
value={value}
onChange={setValue}
renderOption={renderSelectOption}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>{value || 'Select alignment'}</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
큰 데이터셋 (Large data sets)
기본적으로 드롭다운은 ScrollArea.Autosize로 감싸져요. maxDropdownHeight로 최대 높이를 제어할 수 있어요.
import { Button, ComboboxPopover } from '@mantine/core';
const data = Array(50)
.fill(0)
.map((_, index) => `Option ${index}`);
function Demo() {
return (
<ComboboxPopover data={data} maxDropdownHeight={200}>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>Select option</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
드롭다운 열림 상태 제어 (Control dropdown opened state)
dropdownOpened prop으로 드롭다운 상태를 제어할 수 있어요. 추가로 onDropdownOpen과 onDropdownClose 콜백으로 드롭다운 상태 변화에 대응할 수 있어요.
import { Button, ComboboxPopover, Group } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
function Demo() {
const [dropdownOpened, { toggle }] = useDisclosure();
return (
<Group>
<Button onClick={toggle}>Toggle dropdown</Button>
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
dropdownOpened={dropdownOpened}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>Select framework</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
</Group>
);
}
드롭다운 위치 (Dropdown position)
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
comboboxProps={{ position: 'top', middlewares: { flip: false, shift: false } }}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>Open dropdown above</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
드롭다운 너비 (Dropdown width)
기본적으로 드롭다운 너비는 대상 요소와 일치해요. comboboxProps로 커스텀 너비를 설정할 수 있어요.
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
comboboxProps={{ width: 250, position: 'bottom-start' }}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>Select framework</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
드롭다운 애니메이션 (Dropdown animation)
import { Button, ComboboxPopover } from '@mantine/core';
function Demo() {
return (
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
comboboxProps={{ transitionProps: { transition: 'pop', duration: 200 } }}
>
<ComboboxPopover.Target>
<Button variant="default" miw={200}>With animation</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
);
}
폼 제출 (Form submission)
ComboboxPopover는 네이티브 폼 제출을 위해 선택된 값을 가진 숨겨진 input을 렌더링해요. name prop으로 input 이름을 설정해요. 다중 선택에서는 기본적으로 값이 쉼표로 결합되는데, hiddenInputValuesDivider로 구분자를 바꿀 수 있어요.
import { useState } from 'react';
import { Button, ComboboxPopover, Stack, Text } from '@mantine/core';
function Demo() {
const [value, setValue] = useState<string | null>(null);
const [submitted, setSubmitted] = useState('');
return (
<form
onSubmit={(event) => {
event.preventDefault();
const formData = new FormData(event.currentTarget);
setSubmitted(formData.get('framework') as string);
}}
>
<Stack>
<ComboboxPopover
data={['React', 'Angular', 'Vue', 'Svelte']}
value={value}
onChange={setValue}
name="framework"
>
<ComboboxPopover.Target>
<Button variant="default" miw={200} type="button">
{value || 'Select framework'}
</Button>
</ComboboxPopover.Target>
</ComboboxPopover>
<Button type="submit">Submit</Button>
{submitted && <Text size="sm">Submitted value: <b>{submitted}</b></Text>}
</Stack>
</form>
);
}
Styles API
ComboboxPopover는 Styles API를 지원해요. classNames prop으로 내부 요소에 스타일을 추가할 수 있어요.
주요 선택자는 다음과 같아요.
dropdown– 드롭다운 루트 요소options– 옵션 wrapperoption– 옵션empty– 결과 없음 메시지group– 옵션 그룹 wrappergroupLabel– 옵션 그룹 라벨search– 검색 input,searchableprop이 설정되었을 때만 표시돼요
더 알아보기 (Learn more)
- Select — 셀렉트 컴포넌트
- Combobox — 콤보박스 컴포넌트
- MultiSelect — 다중 선택 컴포넌트