feat: enhance Combobox component with multi-select filtering and grouped item handling

This commit is contained in:
Yoga Pangestu 2026-09-03 11:33:13 +07:00
parent 8f9eb5f77a
commit aa47ec494e

View File

@ -13,7 +13,58 @@ import {
InputGroupInput,
} from "@/components/ui/input-group"
const Combobox = ComboboxPrimitive.Root
function isGroupedItems(
items: readonly unknown[]
): items is ReadonlyArray<{ items: readonly unknown[] }> {
return (
items.length > 0 &&
typeof items[0] === "object" &&
items[0] !== null &&
"items" in items[0]
)
}
function Combobox<Value, Multiple extends boolean | undefined = false>({
items,
multiple,
value,
isItemEqualToValue,
...props
}: ComboboxPrimitive.Root.Props<Value, Multiple>) {
// In multi-select mode, once an option is chosen it's represented as a
// chip, so it's removed from the dropdown list instead of staying there
// with a checkmark.
const filteredItems = React.useMemo(() => {
if (!multiple || !items) return items
const selected = value as unknown as Value[] | null | undefined
if (!Array.isArray(selected) || selected.length === 0) return items
const isEqual =
isItemEqualToValue ?? ((a: Value, b: Value) => Object.is(a, b))
const isNotSelected = (item: Value) =>
!selected.some((selectedValue) => isEqual(item, selectedValue))
if (isGroupedItems(items)) {
return items.map((group) => ({
...group,
items: group.items.filter((item) => isNotSelected(item as Value)),
}))
}
return (items as readonly Value[]).filter(isNotSelected)
}, [items, multiple, value, isItemEqualToValue])
return (
<ComboboxPrimitive.Root
items={filteredItems}
multiple={multiple}
value={value}
isItemEqualToValue={isItemEqualToValue}
{...props}
/>
)
}
function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />