컴포넌트
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로 선택한 값을 지웁니다. 초기값과 현재 선택값을 구분해서 확인할 수 있습니다.
알려진 제한
Root에 modal을 지정하면 Base UI가 목록을 닫는 숨은 버튼을 추가합니다. 이 버튼의 이름은 영어 "Dismiss"로 고정되어 있으며 현재 Base UI에서 바꿀 수 없습니다. 화면에는 보이지 않고 보조기기에서만 읽힙니다. 모달이 필요하지 않으면 기본값(비모달)을 사용합니다.
구현 코드
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 { createThemedPortal } from "../../lib/create-themed-portal";
import "./combobox.css";
const ThemedPortal = createThemedPortal(Primitive.Portal);
// 동작과 접근성은 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,
Portal: ThemedPortal,
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
@layer theme, base, components, utilities;
@layer components.ply-base, components.ply;
@import "./button.css";
@import "./badge.css";
@layer components.ply {
/* 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;
min-width: 0;
max-width: 100%;
}
.rbx-chips > .rbx-input {
flex: 1 1 100px;
width: auto;
min-width: 40px;
height: 32px;
border: 0;
padding-inline: 4px;
}
.rbx-chips .rbx-badge > .rbx-icon-button {
width: 20px;
height: 20px;
min-height: 20px;
padding: 2px;
background: transparent;
}
}