컴포넌트
Combobox
목록을 검색하고 항목을 선택합니다.
설치
pnpm dlx ply-ui add combobox icon공통 설치 설정을 먼저 적용합니다.
사용법
Combobox는 검색 입력으로 후보를 좁혀 선택합니다. 검색 없이 하나 또는 여러 값을 고를 때는 Select를 사용합니다.
Root.items와 List 렌더 함수가 같은 데이터를 사용합니다.
value/onValueChange는 선택값, inputValue/onInputValueChange는 검색 문자열입니다.
구성
| 구성 요소 | 역할 |
|---|---|
Root | 후보 항목과 선택값, 입력 문자열을 관리합니다. |
Label | Trigger의 이름입니다. Input의 이름은 HTML label 또는 Field.Label로 연결합니다. |
Input | 항목을 검색하는 입력입니다. |
Trigger / Clear | 목록을 열거나 선택값을 지웁니다. |
Portal / Positioner / Popup | 후보 목록의 위치와 표면을 구성합니다. |
List / Item | 후보 목록과 각 선택 항목입니다. |
ItemIndicator | 선택된 항목의 표시입니다. |
Empty | 검색 결과가 없을 때 표시합니다. |
Group / GroupLabel | 후보를 묶고 그룹 이름을 표시합니다. |
Chips / Chip / ChipRemove | 다중 선택에서 선택한 값들과 각 값의 삭제 버튼을 구성합니다. |
다중 선택
Root.multiple을 지정하면 선택값이 배열이 됩니다. Chips 안에 선택한 항목과 삭제 버튼을 표시합니다.
선택 지우기
Clear로 선택한 값을 지웁니다. 초기값과 현재 선택값을 구분해서 확인할 수 있습니다.
구현 코드
components/ui/combobox.tsx
"use client";
import type { ComponentProps } from "react";
import { Combobox as Primitive } from "@base-ui/react/combobox";
import { withClassName } from "../../lib/cx";
import "./combobox.css";
// 동작과 접근성은 Base UI가 담당합니다. 이 파일은 스타일 연결만 담당합니다.
function ComboboxInput({
className,
...props
}: ComponentProps<typeof Primitive.Input>) {
return (
<Primitive.Input
{...props}
className={withClassName("rbx-input", className)}
/>
);
}
function ComboboxTrigger({
className,
...props
}: ComponentProps<typeof Primitive.Trigger>) {
return (
<Primitive.Trigger
{...props}
className={withClassName("rbx-button", className)}
/>
);
}
function ComboboxPopup({
className,
...props
}: ComponentProps<typeof Primitive.Popup>) {
return (
<Primitive.Popup
{...props}
className={withClassName("rbx-popup", className)}
/>
);
}
function ComboboxPositioner({
className,
...props
}: ComponentProps<typeof Primitive.Positioner>) {
return (
<Primitive.Positioner
{...props}
className={withClassName("rbx-positioner", className)}
/>
);
}
function ComboboxItem({
className,
...props
}: ComponentProps<typeof Primitive.Item>) {
return (
<Primitive.Item
{...props}
className={withClassName("rbx-option", className)}
/>
);
}
function ComboboxItemIndicator({
className,
...props
}: ComponentProps<typeof Primitive.ItemIndicator>) {
return (
<Primitive.ItemIndicator
{...props}
className={withClassName("rbx-option-indicator", className)}
/>
);
}
function ComboboxEmpty({
className,
...props
}: ComponentProps<typeof Primitive.Empty>) {
return (
<Primitive.Empty
{...props}
className={withClassName("rbx-description", className)}
/>
);
}
function ComboboxChips({
className,
...props
}: ComponentProps<typeof Primitive.Chips>) {
return (
<Primitive.Chips
{...props}
className={withClassName("rbx-chips", className)}
/>
);
}
function ComboboxChip({
className,
...props
}: ComponentProps<typeof Primitive.Chip>) {
return (
<Primitive.Chip
{...props}
className={withClassName("rbx-badge", className)}
/>
);
}
function ComboboxChipRemove({
className,
...props
}: ComponentProps<typeof Primitive.ChipRemove>) {
return (
<Primitive.ChipRemove
{...props}
className={withClassName("rbx-icon-button", className)}
/>
);
}
function ComboboxGroupLabel({
className,
...props
}: ComponentProps<typeof Primitive.GroupLabel>) {
return (
<Primitive.GroupLabel
{...props}
className={withClassName("rbx-group-label", className)}
/>
);
}
function ComboboxClear({
className,
...props
}: ComponentProps<typeof Primitive.Clear>) {
return (
<Primitive.Clear
{...props}
className={withClassName("rbx-icon-button", className)}
/>
);
}
// Root/Portal 등 스타일 없는 파트와 제네릭 API는 원본을 그대로 보존합니다.
export const Combobox = {
...Primitive,
Input: ComboboxInput,
Trigger: ComboboxTrigger,
Popup: ComboboxPopup,
Positioner: ComboboxPositioner,
Item: ComboboxItem,
ItemIndicator: ComboboxItemIndicator,
Empty: ComboboxEmpty,
Chips: ComboboxChips,
Chip: ComboboxChip,
ChipRemove: ComboboxChipRemove,
GroupLabel: ComboboxGroupLabel,
Clear: ComboboxClear,
};
components/ui/combobox.css
@import "./button.css";
@import "./badge.css";
/* Combobox: 공통 구조는 theme.css, 컴포넌트 전용 조정은 아래에 작성합니다. */
.rbx-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 6px;
border: 1px solid var(--rbx-color-stroke-default);
border-radius: 8px;
}