Repository navigation
feat: [combobox] add createItems for id-based values #946
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { Combobox as ComboboxPrimitive } from '@base-ui/react'; | ||
|
|
||
| type LabelLookup = (value: unknown) => string | undefined; | ||
|
|
||
| const labelLookups = new WeakMap<object, LabelLookup>(); | ||
|
|
||
| /** | ||
| * Wraps Base UI's `createItems` and records how to label each value, so chips | ||
| * can show labels instead of raw values. | ||
| */ | ||
| export const createItems: typeof ComboboxPrimitive.createItems = ( | ||
| data, | ||
| options | ||
| ) => { | ||
| const collection = ComboboxPrimitive.createItems(data, options); | ||
| labelLookups.set(collection, value => { | ||
| for (const entry of data ?? []) { | ||
| const items = | ||
| typeof entry === 'object' && entry !== null && 'items' in entry | ||
| ? entry.items | ||
|
Comment on lines
+19
to
+20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Check that If an item has 🤖 Prompt for AI Agents |
||
| : [entry]; | ||
| for (const item of items) { | ||
| if (options.getValue(item) === value) return options.getLabel(item); | ||
| } | ||
| } | ||
| return undefined; | ||
| }); | ||
| return collection; | ||
| }; | ||
|
|
||
| /** Returns the label for `value` when `items` came from `createItems`. */ | ||
| export function getItemLabel(items: unknown, value: unknown) { | ||
| if (typeof items !== 'object' || items === null) return undefined; | ||
| return labelLookups.get(items)?.(value); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import { | |
| useState | ||
| } from 'react'; | ||
| import { useFieldContext } from '../field'; | ||
| import { getItemLabel } from './combobox-items'; | ||
|
|
||
| interface ComboboxContextValue<Value = string> { | ||
| multiple: boolean; | ||
|
|
@@ -19,6 +20,7 @@ interface ComboboxContextValue<Value = string> { | |
| inputContainerRef: RefObject<HTMLDivElement | null>; | ||
| value: Value | Value[] | null | undefined; | ||
| onValueChange?: (value: Value | Value[] | null) => void; | ||
| getLabel: (value: Value) => string; | ||
| } | ||
|
|
||
| const ComboboxContext = createContext< | ||
|
|
@@ -37,35 +39,35 @@ export const useComboboxContext = < | |
| return context as ComboboxContextValue<Value>; | ||
| }; | ||
|
|
||
| export interface BaseComboboxRootProps<Value> | ||
| export interface BaseComboboxRootProps<Value, Item = Value> | ||
| extends Omit< | ||
| ComboboxPrimitive.Root.Props<Value, boolean>, | ||
| ComboboxPrimitive.Root.Props<Value, boolean, Item>, | ||
| 'onValueChange' | 'onInputValueChange' | 'multiple' | ||
| > { | ||
| onInputValueChange?: (inputValue: string) => void; | ||
| } | ||
|
|
||
| export interface SingleComboboxProps<Value = string> | ||
| extends BaseComboboxRootProps<Value> { | ||
| export interface SingleComboboxProps<Value = string, Item = Value> | ||
| extends BaseComboboxRootProps<Value, Item> { | ||
| multiple?: false; | ||
| value?: Value | null; | ||
| defaultValue?: Value | null; | ||
| onValueChange?: (value: Value | null) => void; | ||
| } | ||
|
|
||
| export interface MultipleComboboxProps<Value = string> | ||
| extends BaseComboboxRootProps<Value> { | ||
| export interface MultipleComboboxProps<Value = string, Item = Value> | ||
| extends BaseComboboxRootProps<Value, Item> { | ||
| multiple: true; | ||
| value?: Value[]; | ||
| defaultValue?: Value[]; | ||
| onValueChange?: (value: Value[]) => void; | ||
| } | ||
|
|
||
| export type ComboboxRootProps<Value = string> = | ||
| | SingleComboboxProps<Value> | ||
| | MultipleComboboxProps<Value>; | ||
| export type ComboboxRootProps<Value = string, Item = Value> = | ||
| | SingleComboboxProps<Value, Item> | ||
| | MultipleComboboxProps<Value, Item>; | ||
|
|
||
| export const ComboboxRoot = <Value extends unknown | unknown[]>({ | ||
| export const ComboboxRoot = <Value extends unknown | unknown[], Item = Value>({ | ||
| multiple = false, | ||
| children, | ||
| onValueChange, | ||
|
|
@@ -75,7 +77,7 @@ export const ComboboxRoot = <Value extends unknown | unknown[]>({ | |
| items, | ||
| required, | ||
| ...props | ||
| }: ComboboxRootProps<Value>) => { | ||
| }: ComboboxRootProps<Value, Item>) => { | ||
| const fieldContext = useFieldContext(); | ||
| const resolvedRequired = required ?? fieldContext?.required; | ||
|
|
||
|
|
@@ -105,11 +107,11 @@ export const ComboboxRoot = <Value extends unknown | unknown[]>({ | |
| ) => { | ||
| setInternalValue(value); | ||
| if (multiple) { | ||
| (onValueChange as MultipleComboboxProps<Value>['onValueChange'])?.( | ||
| value as Value[] | ||
| ); | ||
| ( | ||
| onValueChange as MultipleComboboxProps<Value, Item>['onValueChange'] | ||
| )?.(value as Value[]); | ||
| } else { | ||
| (onValueChange as SingleComboboxProps<Value>['onValueChange'])?.( | ||
| (onValueChange as SingleComboboxProps<Value, Item>['onValueChange'])?.( | ||
| value as Value | null | ||
| ); | ||
| } | ||
|
|
@@ -124,7 +126,8 @@ export const ComboboxRoot = <Value extends unknown | unknown[]>({ | |
| hasItems: !!items, | ||
| inputContainerRef, | ||
| value: computedValue, | ||
| onValueChange: handleValueChange | ||
| onValueChange: handleValueChange, | ||
| getLabel: (value: Value) => getItemLabel(items, value) ?? String(value) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Keep chip labels consistent with the root’s label rules. If a selected ID is absent from the collection and 🤖 Prompt for AI Agents |
||
| }), | ||
| [multiple, inputValue, items, computedValue, handleValueChange] | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: raystack/apsara
Length of output: 5792
🏁 Script executed:
Repository: raystack/apsara
Length of output: 5492
🌐 Web query:
official Base UI v1.8.0 Combobox createItems ItemCollection Root items type💡 Result:
🌐 Web query:
site:github.com/mui/base-ui/tree/v1.8.0 packages/react/src/combobox itemCollection.ts ComboboxRoot items ItemCollection💡 Result:
🏁 Script executed:
Repository: raystack/apsara
Length of output: 1432
🏁 Script executed:
Repository: raystack/apsara
Length of output: 7800
🏁 Script executed:
Repository: raystack/apsara
Length of output: 6064
🏁 Script executed:
Repository: raystack/apsara
Length of output: 9602
🏁 Script executed:
Repository: raystack/apsara
Length of output: 2448
🏁 Script executed:
Repository: raystack/apsara
Length of output: 4251
Include the
createItemscollection in the documenteditemstype.Combobox.createItemsreturns an opaque collection thatCombobox.Root.itemsaccepts. The declaration currently permits only arrays, so it excludes the documented ID-based usage.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents