diff --git a/resources/js/components/ui/combobox.tsx b/resources/js/components/ui/combobox.tsx index 4b2dfc6..33af737 100644 --- a/resources/js/components/ui/combobox.tsx +++ b/resources/js/components/ui/combobox.tsx @@ -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({ + items, + multiple, + value, + isItemEqualToValue, + ...props +}: ComboboxPrimitive.Root.Props) { + // 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 ( + + ) +} function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { return