@lglab/compose-ui-mcp 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +11 -0
  2. package/dist/assets/llms/accordion.md +184 -0
  3. package/dist/assets/llms/alert-dialog.md +306 -0
  4. package/dist/assets/llms/autocomplete.md +756 -0
  5. package/dist/assets/llms/avatar.md +166 -0
  6. package/dist/assets/llms/badge.md +478 -0
  7. package/dist/assets/llms/button.md +238 -0
  8. package/dist/assets/llms/card.md +264 -0
  9. package/dist/assets/llms/checkbox-group.md +158 -0
  10. package/dist/assets/llms/checkbox.md +83 -0
  11. package/dist/assets/llms/collapsible.md +165 -0
  12. package/dist/assets/llms/combobox.md +1255 -0
  13. package/dist/assets/llms/context-menu.md +371 -0
  14. package/dist/assets/llms/dialog.md +592 -0
  15. package/dist/assets/llms/drawer.md +437 -0
  16. package/dist/assets/llms/field.md +74 -0
  17. package/dist/assets/llms/form.md +1931 -0
  18. package/dist/assets/llms/input.md +47 -0
  19. package/dist/assets/llms/menu.md +484 -0
  20. package/dist/assets/llms/menubar.md +804 -0
  21. package/dist/assets/llms/meter.md +181 -0
  22. package/dist/assets/llms/navigation-menu.md +187 -0
  23. package/dist/assets/llms/number-field.md +243 -0
  24. package/dist/assets/llms/pagination.md +514 -0
  25. package/dist/assets/llms/popover.md +206 -0
  26. package/dist/assets/llms/preview-card.md +146 -0
  27. package/dist/assets/llms/progress.md +60 -0
  28. package/dist/assets/llms/radio-group.md +105 -0
  29. package/dist/assets/llms/scroll-area.md +132 -0
  30. package/dist/assets/llms/select.md +276 -0
  31. package/dist/assets/llms/separator.md +49 -0
  32. package/dist/assets/llms/skeleton.md +96 -0
  33. package/dist/assets/llms/slider.md +161 -0
  34. package/dist/assets/llms/switch.md +101 -0
  35. package/dist/assets/llms/table.md +1325 -0
  36. package/dist/assets/llms/tabs.md +327 -0
  37. package/dist/assets/llms/textarea.md +38 -0
  38. package/dist/assets/llms/toast.md +349 -0
  39. package/dist/assets/llms/toggle-group.md +261 -0
  40. package/dist/assets/llms/toggle.md +161 -0
  41. package/dist/assets/llms/toolbar.md +148 -0
  42. package/dist/assets/llms/tooltip.md +486 -0
  43. package/dist/assets/llms-full.txt +14515 -0
  44. package/dist/assets/llms.txt +65 -0
  45. package/dist/index.d.mts +1 -0
  46. package/dist/index.mjs +161 -0
  47. package/dist/index.mjs.map +1 -0
  48. package/package.json +54 -0
@@ -0,0 +1,1255 @@
1
+ # Combobox
2
+
3
+ An input combined with a list of predefined items to select, with filtering support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lglab/compose-ui
9
+ ```
10
+
11
+ ## Import
12
+
13
+ ```tsx
14
+ import { BaseCombobox as Combobox, ComboboxRoot, ComboboxValue, ComboboxIcon, ComboboxInput, ComboboxControl, ComboboxClear, ComboboxTrigger, ComboboxBackdrop, ComboboxPortal, ComboboxPositioner, ComboboxPopup, ComboboxList, ComboboxEmpty, ComboboxItem, ComboboxItemText, ComboboxItemIndicator, ComboboxGroup, ComboboxGroupLabel, ComboboxCollection, ComboboxSeparator, ComboboxStatus, ComboboxChips, ComboboxChip, ComboboxChipRemove, ComboboxArrow } from '@lglab/compose-ui'
15
+ ```
16
+
17
+ ## Examples
18
+
19
+ ### Default
20
+
21
+ ```tsx
22
+ import {
23
+ ComboboxClear,
24
+ ComboboxControl,
25
+ ComboboxEmpty,
26
+ ComboboxInput,
27
+ ComboboxItem,
28
+ ComboboxItemIndicator,
29
+ ComboboxItemText,
30
+ ComboboxList,
31
+ ComboboxPopup,
32
+ ComboboxPortal,
33
+ ComboboxPositioner,
34
+ ComboboxRoot,
35
+ ComboboxTrigger,
36
+ } from '@lglab/compose-ui/combobox'
37
+ import { FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
38
+ import { Check, ChevronDown, X } from 'lucide-react'
39
+
40
+ interface Fruit {
41
+ label: string
42
+ value: string
43
+ }
44
+
45
+ const fruits: Fruit[] = [
46
+ { label: 'Apple', value: 'apple' },
47
+ { label: 'Banana', value: 'banana' },
48
+ { label: 'Orange', value: 'orange' },
49
+ { label: 'Pineapple', value: 'pineapple' },
50
+ { label: 'Grape', value: 'grape' },
51
+ { label: 'Mango', value: 'mango' },
52
+ { label: 'Strawberry', value: 'strawberry' },
53
+ { label: 'Blueberry', value: 'blueberry' },
54
+ { label: 'Raspberry', value: 'raspberry' },
55
+ { label: 'Blackberry', value: 'blackberry' },
56
+ { label: 'Cherry', value: 'cherry' },
57
+ { label: 'Peach', value: 'peach' },
58
+ { label: 'Pear', value: 'pear' },
59
+ { label: 'Plum', value: 'plum' },
60
+ { label: 'Kiwi', value: 'kiwi' },
61
+ { label: 'Watermelon', value: 'watermelon' },
62
+ { label: 'Cantaloupe', value: 'cantaloupe' },
63
+ { label: 'Honeydew', value: 'honeydew' },
64
+ { label: 'Papaya', value: 'papaya' },
65
+ { label: 'Guava', value: 'guava' },
66
+ { label: 'Lychee', value: 'lychee' },
67
+ { label: 'Pomegranate', value: 'pomegranate' },
68
+ { label: 'Apricot', value: 'apricot' },
69
+ { label: 'Grapefruit', value: 'grapefruit' },
70
+ { label: 'Passionfruit', value: 'passionfruit' },
71
+ ]
72
+
73
+ export default function DefaultExample() {
74
+ return (
75
+ <FieldRoot>
76
+ <FieldLabel>Choose a fruit</FieldLabel>
77
+ <ComboboxRoot items={fruits}>
78
+ <ComboboxControl>
79
+ <ComboboxInput placeholder='e.g. Apple' />
80
+ <ComboboxClear aria-label='Clear selection'>
81
+ <X className='size-4' />
82
+ </ComboboxClear>
83
+ <ComboboxTrigger aria-label='Open popup'>
84
+ <ChevronDown className='size-4' />
85
+ </ComboboxTrigger>
86
+ </ComboboxControl>
87
+ <ComboboxPortal>
88
+ <ComboboxPositioner>
89
+ <ComboboxPopup>
90
+ <ComboboxEmpty>No fruits found.</ComboboxEmpty>
91
+ <ComboboxList>
92
+ {(item: Fruit) => (
93
+ <ComboboxItem key={item.value} value={item}>
94
+ <ComboboxItemText>{item.label}</ComboboxItemText>
95
+ <ComboboxItemIndicator>
96
+ <Check className='size-3.5' />
97
+ </ComboboxItemIndicator>
98
+ </ComboboxItem>
99
+ )}
100
+ </ComboboxList>
101
+ </ComboboxPopup>
102
+ </ComboboxPositioner>
103
+ </ComboboxPortal>
104
+ </ComboboxRoot>
105
+ </FieldRoot>
106
+ )
107
+ }
108
+ ```
109
+
110
+ ### Multiselect
111
+
112
+ ```tsx
113
+ import {
114
+ ComboboxChip,
115
+ ComboboxChipRemove,
116
+ ComboboxChips,
117
+ ComboboxEmpty,
118
+ ComboboxInput,
119
+ ComboboxItem,
120
+ ComboboxItemIndicator,
121
+ ComboboxItemText,
122
+ ComboboxList,
123
+ ComboboxPopup,
124
+ ComboboxPortal,
125
+ ComboboxPositioner,
126
+ ComboboxRoot,
127
+ ComboboxValue,
128
+ } from '@lglab/compose-ui/combobox'
129
+ import { FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
130
+ import { Check, X } from 'lucide-react'
131
+ import * as React from 'react'
132
+
133
+ interface ProgrammingLanguage {
134
+ id: string
135
+ value: string
136
+ }
137
+
138
+ const langs: ProgrammingLanguage[] = [
139
+ { id: 'js', value: 'JavaScript' },
140
+ { id: 'ts', value: 'TypeScript' },
141
+ { id: 'py', value: 'Python' },
142
+ { id: 'java', value: 'Java' },
143
+ { id: 'cpp', value: 'C++' },
144
+ { id: 'cs', value: 'C#' },
145
+ { id: 'php', value: 'PHP' },
146
+ { id: 'ruby', value: 'Ruby' },
147
+ { id: 'go', value: 'Go' },
148
+ { id: 'rust', value: 'Rust' },
149
+ { id: 'swift', value: 'Swift' },
150
+ ]
151
+
152
+ export default function MultiselectExample() {
153
+ const containerRef = React.useRef<HTMLDivElement | null>(null)
154
+
155
+ return (
156
+ <FieldRoot>
157
+ <FieldLabel>Programming languages</FieldLabel>
158
+ <ComboboxRoot items={langs} multiple>
159
+ <ComboboxChips ref={containerRef} className='max-w-xs'>
160
+ <ComboboxValue>
161
+ {(value: ProgrammingLanguage[]) => (
162
+ <React.Fragment>
163
+ {value.map((language) => (
164
+ <ComboboxChip key={language.id} aria-label={language.value}>
165
+ {language.value}
166
+ <ComboboxChipRemove aria-label='Remove'>
167
+ <X className='size-3' />
168
+ </ComboboxChipRemove>
169
+ </ComboboxChip>
170
+ ))}
171
+ <ComboboxInput placeholder={value.length > 0 ? '' : 'e.g. TypeScript'} />
172
+ </React.Fragment>
173
+ )}
174
+ </ComboboxValue>
175
+ </ComboboxChips>
176
+ <ComboboxPortal>
177
+ <ComboboxPositioner sideOffset={4} anchor={containerRef}>
178
+ <ComboboxPopup>
179
+ <ComboboxEmpty>No languages found.</ComboboxEmpty>
180
+ <ComboboxList>
181
+ {(language: ProgrammingLanguage) => (
182
+ <ComboboxItem key={language.id} value={language}>
183
+ <ComboboxItemText>{language.value}</ComboboxItemText>
184
+ <ComboboxItemIndicator>
185
+ <Check className='size-3.5' />
186
+ </ComboboxItemIndicator>
187
+ </ComboboxItem>
188
+ )}
189
+ </ComboboxList>
190
+ </ComboboxPopup>
191
+ </ComboboxPositioner>
192
+ </ComboboxPortal>
193
+ </ComboboxRoot>
194
+ </FieldRoot>
195
+ )
196
+ }
197
+ ```
198
+
199
+ ### Grouped
200
+
201
+ ```tsx
202
+ import {
203
+ ComboboxClear,
204
+ ComboboxCollection,
205
+ ComboboxControl,
206
+ ComboboxEmpty,
207
+ ComboboxGroup,
208
+ ComboboxGroupLabel,
209
+ ComboboxInput,
210
+ ComboboxItem,
211
+ ComboboxItemIndicator,
212
+ ComboboxItemText,
213
+ ComboboxList,
214
+ ComboboxPopup,
215
+ ComboboxPortal,
216
+ ComboboxPositioner,
217
+ ComboboxRoot,
218
+ ComboboxSeparator,
219
+ ComboboxTrigger,
220
+ } from '@lglab/compose-ui/combobox'
221
+ import { FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
222
+ import { Check, ChevronDown, X } from 'lucide-react'
223
+ import * as React from 'react'
224
+
225
+ interface Produce {
226
+ id: string
227
+ label: string
228
+ group: 'Fruits' | 'Vegetables' | 'Grains'
229
+ }
230
+
231
+ interface ProduceGroup {
232
+ value: string
233
+ items: Produce[]
234
+ }
235
+
236
+ const produceData: Produce[] = [
237
+ { id: 'fruit-apple', label: 'Apple', group: 'Fruits' },
238
+ { id: 'fruit-banana', label: 'Banana', group: 'Fruits' },
239
+ { id: 'fruit-mango', label: 'Mango', group: 'Fruits' },
240
+ { id: 'fruit-kiwi', label: 'Kiwi', group: 'Fruits' },
241
+ { id: 'veg-broccoli', label: 'Broccoli', group: 'Vegetables' },
242
+ { id: 'veg-carrot', label: 'Carrot', group: 'Vegetables' },
243
+ { id: 'veg-cauliflower', label: 'Cauliflower', group: 'Vegetables' },
244
+ { id: 'veg-cucumber', label: 'Cucumber', group: 'Vegetables' },
245
+ { id: 'veg-kale', label: 'Kale', group: 'Vegetables' },
246
+ { id: 'veg-pepper', label: 'Bell pepper', group: 'Vegetables' },
247
+ { id: 'veg-spinach', label: 'Spinach', group: 'Vegetables' },
248
+ { id: 'veg-zucchini', label: 'Zucchini', group: 'Vegetables' },
249
+ { id: 'grain-rice', label: 'Rice', group: 'Grains' },
250
+ { id: 'grain-wheat', label: 'Wheat', group: 'Grains' },
251
+ { id: 'grain-oats', label: 'Oats', group: 'Grains' },
252
+ { id: 'grain-barley', label: 'Barley', group: 'Grains' },
253
+ { id: 'grain-quinoa', label: 'Quinoa', group: 'Grains' },
254
+ ]
255
+
256
+ function groupProduce(items: Produce[]): ProduceGroup[] {
257
+ const groups: Record<string, Produce[]> = {}
258
+ items.forEach((item) => {
259
+ ;(groups[item.group] ??= []).push(item)
260
+ })
261
+ const order = ['Fruits', 'Vegetables', 'Grains']
262
+ return order.map((value) => ({ value, items: groups[value] ?? [] }))
263
+ }
264
+
265
+ const groupedProduce: ProduceGroup[] = groupProduce(produceData)
266
+
267
+ export default function GroupedExample() {
268
+ return (
269
+ <FieldRoot>
270
+ <FieldLabel>Select produce</FieldLabel>
271
+ <ComboboxRoot items={groupedProduce}>
272
+ <ComboboxControl>
273
+ <ComboboxInput placeholder='e.g. Mango' />
274
+ <ComboboxClear aria-label='Clear selection'>
275
+ <X className='size-4' />
276
+ </ComboboxClear>
277
+ <ComboboxTrigger aria-label='Open popup'>
278
+ <ChevronDown className='size-4' />
279
+ </ComboboxTrigger>
280
+ </ComboboxControl>
281
+ <ComboboxPortal>
282
+ <ComboboxPositioner>
283
+ <ComboboxPopup>
284
+ <ComboboxEmpty>No produce found.</ComboboxEmpty>
285
+ <ComboboxList>
286
+ {(group: ProduceGroup, index: number) => (
287
+ <React.Fragment key={group.value}>
288
+ <ComboboxGroup items={group.items}>
289
+ <ComboboxGroupLabel>{group.value}</ComboboxGroupLabel>
290
+ <ComboboxCollection>
291
+ {(item: Produce) => (
292
+ <ComboboxItem key={item.id} value={item}>
293
+ <ComboboxItemText>{item.label}</ComboboxItemText>
294
+ <ComboboxItemIndicator>
295
+ <Check className='size-3.5' />
296
+ </ComboboxItemIndicator>
297
+ </ComboboxItem>
298
+ )}
299
+ </ComboboxCollection>
300
+ </ComboboxGroup>
301
+ {index < groupedProduce.length - 1 && <ComboboxSeparator />}
302
+ </React.Fragment>
303
+ )}
304
+ </ComboboxList>
305
+ </ComboboxPopup>
306
+ </ComboboxPositioner>
307
+ </ComboboxPortal>
308
+ </ComboboxRoot>
309
+ </FieldRoot>
310
+ )
311
+ }
312
+ ```
313
+
314
+ ### Popup
315
+
316
+ ```tsx
317
+ import { Button } from '@lglab/compose-ui/button'
318
+ import {
319
+ ComboboxEmpty,
320
+ ComboboxIcon,
321
+ ComboboxInput,
322
+ ComboboxItem,
323
+ ComboboxItemIndicator,
324
+ ComboboxItemText,
325
+ ComboboxList,
326
+ ComboboxPopup,
327
+ ComboboxPortal,
328
+ ComboboxPositioner,
329
+ ComboboxRoot,
330
+ ComboboxTrigger,
331
+ ComboboxValue,
332
+ } from '@lglab/compose-ui/combobox'
333
+ import { FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
334
+ import { Check, ChevronsUpDown } from 'lucide-react'
335
+
336
+ interface Country {
337
+ label: string
338
+ value: string
339
+ }
340
+
341
+ const countries: Country[] = [
342
+ { label: 'Argentina', value: 'ar' },
343
+ { label: 'Australia', value: 'au' },
344
+ { label: 'Brazil', value: 'br' },
345
+ { label: 'Canada', value: 'ca' },
346
+ { label: 'China', value: 'cn' },
347
+ { label: 'France', value: 'fr' },
348
+ { label: 'Germany', value: 'de' },
349
+ { label: 'India', value: 'in' },
350
+ { label: 'Italy', value: 'it' },
351
+ { label: 'Japan', value: 'jp' },
352
+ { label: 'Mexico', value: 'mx' },
353
+ { label: 'Netherlands', value: 'nl' },
354
+ { label: 'Poland', value: 'pl' },
355
+ { label: 'South Korea', value: 'kr' },
356
+ { label: 'Spain', value: 'es' },
357
+ { label: 'Sweden', value: 'se' },
358
+ { label: 'Switzerland', value: 'ch' },
359
+ { label: 'United Kingdom', value: 'gb' },
360
+ { label: 'United States', value: 'us' },
361
+ ]
362
+
363
+ export default function InputInsidePopupExample() {
364
+ return (
365
+ <FieldRoot className='w-64'>
366
+ <FieldLabel render={<div />} nativeLabel={false}>
367
+ Country
368
+ </FieldLabel>
369
+ <ComboboxRoot items={countries}>
370
+ <ComboboxTrigger
371
+ render={(props) => (
372
+ <Button {...props} className='justify-between font-normal' variant='outline'>
373
+ <ComboboxValue placeholder='Select a country' />
374
+ <ComboboxIcon>
375
+ <ChevronsUpDown className='size-4' />
376
+ </ComboboxIcon>
377
+ </Button>
378
+ )}
379
+ />
380
+
381
+ <ComboboxPortal>
382
+ <ComboboxPositioner align='start'>
383
+ <ComboboxPopup>
384
+ <ComboboxInput placeholder='e.g. United Kingdom' />
385
+ <ComboboxEmpty>No countries found.</ComboboxEmpty>
386
+ <ComboboxList>
387
+ {(item: Country) => (
388
+ <ComboboxItem key={item.value} value={item}>
389
+ <ComboboxItemText>{item.label}</ComboboxItemText>
390
+ <ComboboxItemIndicator>
391
+ <Check className='size-3.5' />
392
+ </ComboboxItemIndicator>
393
+ </ComboboxItem>
394
+ )}
395
+ </ComboboxList>
396
+ </ComboboxPopup>
397
+ </ComboboxPositioner>
398
+ </ComboboxPortal>
399
+ </ComboboxRoot>
400
+ </FieldRoot>
401
+ )
402
+ }
403
+ ```
404
+
405
+ ### Async search
406
+
407
+ ```tsx
408
+ import {
409
+ Combobox,
410
+ ComboboxClear,
411
+ ComboboxControl,
412
+ ComboboxEmpty,
413
+ ComboboxInput,
414
+ ComboboxItem,
415
+ ComboboxItemIndicator,
416
+ ComboboxItemText,
417
+ ComboboxList,
418
+ ComboboxPopup,
419
+ ComboboxPortal,
420
+ ComboboxPositioner,
421
+ ComboboxRoot,
422
+ ComboboxStatus,
423
+ ComboboxTrigger,
424
+ } from '@lglab/compose-ui/combobox'
425
+ import { FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
426
+ import { Check, ChevronDown, Loader2, X } from 'lucide-react'
427
+ import { useMemo, useRef, useState, useTransition } from 'react'
428
+
429
+ interface DirectoryUser {
430
+ id: string
431
+ name: string
432
+ email: string
433
+ title: string
434
+ }
435
+
436
+ export default function AsyncSearchExample() {
437
+ const [searchResults, setSearchResults] = useState<DirectoryUser[]>([])
438
+ const [selectedValue, setSelectedValue] = useState<DirectoryUser | null>(null)
439
+ const [searchValue, setSearchValue] = useState('')
440
+ const [error, setError] = useState<string | null>(null)
441
+ const [isPending, startTransition] = useTransition()
442
+
443
+ const { contains } = Combobox.useFilter()
444
+
445
+ const abortControllerRef = useRef<AbortController | null>(null)
446
+
447
+ const trimmedSearchValue = searchValue.trim()
448
+
449
+ const items = useMemo(() => {
450
+ if (!selectedValue || searchResults.some((user) => user.id === selectedValue.id)) {
451
+ return searchResults
452
+ }
453
+ return [...searchResults, selectedValue]
454
+ }, [searchResults, selectedValue])
455
+
456
+ function getStatus() {
457
+ if (isPending) {
458
+ return (
459
+ <>
460
+ <Loader2 className='size-3 animate-spin' />
461
+ Searching…
462
+ </>
463
+ )
464
+ }
465
+
466
+ if (error) {
467
+ return error
468
+ }
469
+
470
+ if (trimmedSearchValue === '') {
471
+ return selectedValue ? null : 'Start typing to search people…'
472
+ }
473
+
474
+ if (searchResults.length === 0) {
475
+ return `No matches for "${trimmedSearchValue}".`
476
+ }
477
+
478
+ return null
479
+ }
480
+
481
+ function getEmptyMessage() {
482
+ if (trimmedSearchValue === '' || isPending || searchResults.length > 0 || error) {
483
+ return null
484
+ }
485
+ return 'Try a different search term.'
486
+ }
487
+
488
+ return (
489
+ <FieldRoot className='w-80'>
490
+ <FieldLabel>Assign reviewer</FieldLabel>
491
+ <ComboboxRoot
492
+ items={items}
493
+ itemToStringLabel={(user: DirectoryUser) => user.name}
494
+ filter={null}
495
+ onOpenChangeComplete={(open) => {
496
+ if (!open && selectedValue) {
497
+ setSearchResults([selectedValue])
498
+ }
499
+ }}
500
+ onValueChange={(nextSelectedValue) => {
501
+ setSelectedValue(nextSelectedValue)
502
+ setSearchValue('')
503
+ setError(null)
504
+ }}
505
+ onInputValueChange={(nextSearchValue, { reason }) => {
506
+ setSearchValue(nextSearchValue)
507
+
508
+ if (nextSearchValue === '') {
509
+ setSearchResults([])
510
+ setError(null)
511
+ return
512
+ }
513
+
514
+ if (reason === 'item-press') {
515
+ return
516
+ }
517
+
518
+ const controller = new AbortController()
519
+ abortControllerRef.current?.abort()
520
+ abortControllerRef.current = controller
521
+
522
+ startTransition(async () => {
523
+ setError(null)
524
+
525
+ const result = await searchUsers(nextSearchValue, contains)
526
+
527
+ if (controller.signal.aborted) {
528
+ return
529
+ }
530
+
531
+ startTransition(() => {
532
+ setSearchResults(result.users)
533
+ setError(result.error)
534
+ })
535
+ })
536
+ }}
537
+ >
538
+ <ComboboxControl>
539
+ <ComboboxInput placeholder='e.g. Michael' />
540
+ <ComboboxClear aria-label='Clear selection'>
541
+ <X className='size-4' />
542
+ </ComboboxClear>
543
+ <ComboboxTrigger aria-label='Open popup'>
544
+ <ChevronDown className='size-4' />
545
+ </ComboboxTrigger>
546
+ </ComboboxControl>
547
+
548
+ <ComboboxPortal>
549
+ <ComboboxPositioner>
550
+ <ComboboxPopup aria-busy={isPending || undefined}>
551
+ <ComboboxStatus>{getStatus()}</ComboboxStatus>
552
+ <ComboboxEmpty>{getEmptyMessage()}</ComboboxEmpty>
553
+ <ComboboxList>
554
+ {(user: DirectoryUser) => (
555
+ <ComboboxItem key={user.id} value={user}>
556
+ <div className='flex flex-col gap-0.5'>
557
+ <ComboboxItemText className='font-medium'>
558
+ {user.name}
559
+ </ComboboxItemText>
560
+ <div className='text-xs text-muted-foreground font-medium'>
561
+ {user.title}
562
+ </div>
563
+ <div className='text-xs text-muted-foreground'>{user.email}</div>
564
+ </div>
565
+ <ComboboxItemIndicator>
566
+ <Check className='size-3.5' />
567
+ </ComboboxItemIndicator>
568
+ </ComboboxItem>
569
+ )}
570
+ </ComboboxList>
571
+ </ComboboxPopup>
572
+ </ComboboxPositioner>
573
+ </ComboboxPortal>
574
+ </ComboboxRoot>
575
+ </FieldRoot>
576
+ )
577
+ }
578
+
579
+ async function searchUsers(
580
+ query: string,
581
+ filter: (item: string, query: string) => boolean,
582
+ ): Promise<{ users: DirectoryUser[]; error: string | null }> {
583
+ await new Promise((resolve) => {
584
+ setTimeout(resolve, Math.random() * 500 + 100)
585
+ })
586
+
587
+ const users = allUsers.filter((user) => {
588
+ return (
589
+ filter(user.name, query) || filter(user.email, query) || filter(user.title, query)
590
+ )
591
+ })
592
+
593
+ return {
594
+ users,
595
+ error: null,
596
+ }
597
+ }
598
+
599
+ const allUsers: DirectoryUser[] = [
600
+ {
601
+ id: 'leslie-alexander',
602
+ name: 'Leslie Alexander',
603
+ email: 'leslie.alexander@example.com',
604
+ title: 'Product Manager',
605
+ },
606
+ {
607
+ id: 'kathryn-murphy',
608
+ name: 'Kathryn Murphy',
609
+ email: 'kathryn.murphy@example.com',
610
+ title: 'Marketing Lead',
611
+ },
612
+ {
613
+ id: 'courtney-henry',
614
+ name: 'Courtney Henry',
615
+ email: 'courtney.henry@example.com',
616
+ title: 'Design Systems',
617
+ },
618
+ {
619
+ id: 'michael-foster',
620
+ name: 'Michael Foster',
621
+ email: 'michael.foster@example.com',
622
+ title: 'Engineering Manager',
623
+ },
624
+ {
625
+ id: 'lindsay-walton',
626
+ name: 'Lindsay Walton',
627
+ email: 'lindsay.walton@example.com',
628
+ title: 'Product Designer',
629
+ },
630
+ {
631
+ id: 'tom-cook',
632
+ name: 'Tom Cook',
633
+ email: 'tom.cook@example.com',
634
+ title: 'Frontend Engineer',
635
+ },
636
+ {
637
+ id: 'whitney-francis',
638
+ name: 'Whitney Francis',
639
+ email: 'whitney.francis@example.com',
640
+ title: 'Customer Success',
641
+ },
642
+ {
643
+ id: 'jacob-jones',
644
+ name: 'Jacob Jones',
645
+ email: 'jacob.jones@example.com',
646
+ title: 'Security Engineer',
647
+ },
648
+ {
649
+ id: 'arlene-mccoy',
650
+ name: 'Arlene McCoy',
651
+ email: 'arlene.mccoy@example.com',
652
+ title: 'Data Analyst',
653
+ },
654
+ {
655
+ id: 'marvin-mckinney',
656
+ name: 'Marvin McKinney',
657
+ email: 'marvin.mckinney@example.com',
658
+ title: 'QA Specialist',
659
+ },
660
+ {
661
+ id: 'eleanor-pena',
662
+ name: 'Eleanor Pena',
663
+ email: 'eleanor.pena@example.com',
664
+ title: 'Operations',
665
+ },
666
+ {
667
+ id: 'jerome-bell',
668
+ name: 'Jerome Bell',
669
+ email: 'jerome.bell@example.com',
670
+ title: 'DevOps Engineer',
671
+ },
672
+ ]
673
+ ```
674
+
675
+ ### Async search Multiple
676
+
677
+ ```tsx
678
+ import {
679
+ Combobox,
680
+ ComboboxChip,
681
+ ComboboxChipRemove,
682
+ ComboboxChips,
683
+ ComboboxEmpty,
684
+ ComboboxInput,
685
+ ComboboxItem,
686
+ ComboboxItemIndicator,
687
+ ComboboxItemText,
688
+ ComboboxList,
689
+ ComboboxPopup,
690
+ ComboboxPortal,
691
+ ComboboxPositioner,
692
+ ComboboxRoot,
693
+ ComboboxStatus,
694
+ ComboboxValue,
695
+ } from '@lglab/compose-ui/combobox'
696
+ import { FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
697
+ import { Check, Loader2, X } from 'lucide-react'
698
+ import { useMemo, useRef, useState, useTransition } from 'react'
699
+
700
+ interface DirectoryUser {
701
+ id: string
702
+ name: string
703
+ email: string
704
+ title: string
705
+ }
706
+
707
+ export default function AsyncSearchMultipleExample() {
708
+ const [searchResults, setSearchResults] = useState<DirectoryUser[]>([])
709
+ const [selectedValues, setSelectedValues] = useState<DirectoryUser[]>([])
710
+ const [searchValue, setSearchValue] = useState('')
711
+ const [error, setError] = useState<string | null>(null)
712
+ const [blockStartStatus, setBlockStartStatus] = useState(false)
713
+ const [isPending, startTransition] = useTransition()
714
+
715
+ const { contains } = Combobox.useFilter()
716
+
717
+ const abortControllerRef = useRef<AbortController | null>(null)
718
+ const selectedValuesRef = useRef<DirectoryUser[]>([])
719
+
720
+ const trimmedSearchValue = searchValue.trim()
721
+
722
+ const items = useMemo(() => {
723
+ if (selectedValues.length === 0) {
724
+ return searchResults
725
+ }
726
+
727
+ const merged = [...searchResults]
728
+
729
+ selectedValues.forEach((user) => {
730
+ if (!searchResults.some((result) => result.id === user.id)) {
731
+ merged.push(user)
732
+ }
733
+ })
734
+
735
+ return merged
736
+ }, [searchResults, selectedValues])
737
+
738
+ function getStatus() {
739
+ if (isPending) {
740
+ return (
741
+ <>
742
+ <Loader2 className='size-3 animate-spin' />
743
+ Searching…
744
+ </>
745
+ )
746
+ }
747
+
748
+ if (error) {
749
+ return error
750
+ }
751
+
752
+ if (trimmedSearchValue === '' && !blockStartStatus) {
753
+ return selectedValues.length > 0 ? null : 'Start typing to search people…'
754
+ }
755
+
756
+ if (searchResults.length === 0 && !blockStartStatus) {
757
+ return `No matches for "${trimmedSearchValue}".`
758
+ }
759
+
760
+ return null
761
+ }
762
+
763
+ function getEmptyMessage() {
764
+ if (trimmedSearchValue === '' || isPending || searchResults.length > 0 || error) {
765
+ return null
766
+ }
767
+ return 'Try a different search term.'
768
+ }
769
+
770
+ return (
771
+ <FieldRoot className='w-80'>
772
+ <FieldLabel>Assign reviewers</FieldLabel>
773
+ <ComboboxRoot
774
+ items={items}
775
+ itemToStringLabel={(user: DirectoryUser) => user.name}
776
+ multiple
777
+ filter={null}
778
+ onOpenChangeComplete={(open) => {
779
+ if (!open) {
780
+ setSearchResults(selectedValuesRef.current)
781
+ setBlockStartStatus(false)
782
+ }
783
+ }}
784
+ onValueChange={(nextSelectedValues) => {
785
+ selectedValuesRef.current = nextSelectedValues
786
+ setSelectedValues(nextSelectedValues)
787
+ setSearchValue('')
788
+ setError(null)
789
+
790
+ if (nextSelectedValues.length === 0) {
791
+ setSearchResults([])
792
+ setBlockStartStatus(false)
793
+ } else {
794
+ setBlockStartStatus(true)
795
+ }
796
+ }}
797
+ onInputValueChange={(nextSearchValue, { reason }) => {
798
+ setSearchValue(nextSearchValue)
799
+
800
+ const controller = new AbortController()
801
+ abortControllerRef.current?.abort()
802
+ abortControllerRef.current = controller
803
+
804
+ if (nextSearchValue === '') {
805
+ setSearchResults(selectedValuesRef.current)
806
+ setError(null)
807
+ setBlockStartStatus(false)
808
+ return
809
+ }
810
+
811
+ if (reason === 'item-press') {
812
+ return
813
+ }
814
+
815
+ startTransition(async () => {
816
+ setError(null)
817
+
818
+ const result = await searchUsers(nextSearchValue, contains)
819
+
820
+ if (controller.signal.aborted) {
821
+ return
822
+ }
823
+
824
+ startTransition(() => {
825
+ setSearchResults(result.users)
826
+ setError(result.error)
827
+ })
828
+ })
829
+ }}
830
+ >
831
+ <ComboboxChips>
832
+ <ComboboxValue>
833
+ {(value: DirectoryUser[]) => (
834
+ <>
835
+ {value.map((user) => (
836
+ <ComboboxChip key={user.id} aria-label={user.name}>
837
+ {user.name}
838
+ <ComboboxChipRemove aria-label='Remove'>
839
+ <X className='size-3' />
840
+ </ComboboxChipRemove>
841
+ </ComboboxChip>
842
+ ))}
843
+ <ComboboxInput placeholder={value.length > 0 ? '' : 'e.g. Michael'} />
844
+ </>
845
+ )}
846
+ </ComboboxValue>
847
+ </ComboboxChips>
848
+
849
+ <ComboboxPortal>
850
+ <ComboboxPositioner>
851
+ <ComboboxPopup aria-busy={isPending || undefined}>
852
+ <ComboboxStatus>{getStatus()}</ComboboxStatus>
853
+ <ComboboxEmpty>{getEmptyMessage()}</ComboboxEmpty>
854
+ <ComboboxList>
855
+ {(user: DirectoryUser) => (
856
+ <ComboboxItem key={user.id} value={user}>
857
+ <div className='flex flex-col gap-0.5'>
858
+ <ComboboxItemText className='font-medium'>
859
+ {user.name}
860
+ </ComboboxItemText>
861
+ <div className='text-xs text-muted-foreground font-medium'>
862
+ {user.title}
863
+ </div>
864
+ <div className='text-xs text-muted-foreground'>{user.email}</div>
865
+ </div>
866
+ <ComboboxItemIndicator>
867
+ <Check className='size-3.5' />
868
+ </ComboboxItemIndicator>
869
+ </ComboboxItem>
870
+ )}
871
+ </ComboboxList>
872
+ </ComboboxPopup>
873
+ </ComboboxPositioner>
874
+ </ComboboxPortal>
875
+ </ComboboxRoot>
876
+ </FieldRoot>
877
+ )
878
+ }
879
+
880
+ async function searchUsers(
881
+ query: string,
882
+ filter: (item: string, query: string) => boolean,
883
+ ): Promise<{ users: DirectoryUser[]; error: string | null }> {
884
+ await new Promise((resolve) => {
885
+ setTimeout(resolve, Math.random() * 500 + 100)
886
+ })
887
+
888
+ const users = allUsers.filter((user) => {
889
+ return (
890
+ filter(user.name, query) || filter(user.email, query) || filter(user.title, query)
891
+ )
892
+ })
893
+
894
+ return {
895
+ users,
896
+ error: null,
897
+ }
898
+ }
899
+
900
+ const allUsers: DirectoryUser[] = [
901
+ {
902
+ id: 'leslie-alexander',
903
+ name: 'Leslie Alexander',
904
+ email: 'leslie.alexander@example.com',
905
+ title: 'Product Manager',
906
+ },
907
+ {
908
+ id: 'kathryn-murphy',
909
+ name: 'Kathryn Murphy',
910
+ email: 'kathryn.murphy@example.com',
911
+ title: 'Marketing Lead',
912
+ },
913
+ {
914
+ id: 'courtney-henry',
915
+ name: 'Courtney Henry',
916
+ email: 'courtney.henry@example.com',
917
+ title: 'Design Systems',
918
+ },
919
+ {
920
+ id: 'michael-foster',
921
+ name: 'Michael Foster',
922
+ email: 'michael.foster@example.com',
923
+ title: 'Engineering Manager',
924
+ },
925
+ {
926
+ id: 'lindsay-walton',
927
+ name: 'Lindsay Walton',
928
+ email: 'lindsay.walton@example.com',
929
+ title: 'Product Designer',
930
+ },
931
+ {
932
+ id: 'tom-cook',
933
+ name: 'Tom Cook',
934
+ email: 'tom.cook@example.com',
935
+ title: 'Frontend Engineer',
936
+ },
937
+ {
938
+ id: 'whitney-francis',
939
+ name: 'Whitney Francis',
940
+ email: 'whitney.francis@example.com',
941
+ title: 'Customer Success',
942
+ },
943
+ {
944
+ id: 'jacob-jones',
945
+ name: 'Jacob Jones',
946
+ email: 'jacob.jones@example.com',
947
+ title: 'Security Engineer',
948
+ },
949
+ {
950
+ id: 'arlene-mccoy',
951
+ name: 'Arlene McCoy',
952
+ email: 'arlene.mccoy@example.com',
953
+ title: 'Data Analyst',
954
+ },
955
+ {
956
+ id: 'marvin-mckinney',
957
+ name: 'Marvin McKinney',
958
+ email: 'marvin.mckinney@example.com',
959
+ title: 'QA Specialist',
960
+ },
961
+ {
962
+ id: 'eleanor-pena',
963
+ name: 'Eleanor Pena',
964
+ email: 'eleanor.pena@example.com',
965
+ title: 'Operations',
966
+ },
967
+ {
968
+ id: 'jerome-bell',
969
+ name: 'Jerome Bell',
970
+ email: 'jerome.bell@example.com',
971
+ title: 'DevOps Engineer',
972
+ },
973
+ ]
974
+ ```
975
+
976
+ ### Creatable
977
+
978
+ ```tsx
979
+ import { Button } from '@lglab/compose-ui/button'
980
+ import {
981
+ ComboboxChip,
982
+ ComboboxChipRemove,
983
+ ComboboxChips,
984
+ ComboboxEmpty,
985
+ ComboboxInput,
986
+ ComboboxItem,
987
+ ComboboxItemIndicator,
988
+ ComboboxItemText,
989
+ ComboboxList,
990
+ ComboboxPopup,
991
+ ComboboxPortal,
992
+ ComboboxPositioner,
993
+ ComboboxRoot,
994
+ ComboboxValue,
995
+ } from '@lglab/compose-ui/combobox'
996
+ import {
997
+ DialogBackdrop,
998
+ DialogClose,
999
+ DialogDescription,
1000
+ DialogFooter,
1001
+ DialogHeader,
1002
+ DialogPopup,
1003
+ DialogPortal,
1004
+ DialogRoot,
1005
+ DialogTitle,
1006
+ } from '@lglab/compose-ui/dialog'
1007
+ import { FieldControl, FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
1008
+ import { FormRoot } from '@lglab/compose-ui/form'
1009
+ import { Check, Plus, X } from 'lucide-react'
1010
+ import {
1011
+ type ChangeEvent,
1012
+ type FormEvent,
1013
+ type KeyboardEvent,
1014
+ useMemo,
1015
+ useRef,
1016
+ useState,
1017
+ } from 'react'
1018
+
1019
+ const normalize = (str: string) => str.trim().toLocaleLowerCase()
1020
+
1021
+ function generateUniqueId(baseId: string, existingIds: Set<string>): string {
1022
+ if (!existingIds.has(baseId)) return baseId
1023
+ let counter = 2
1024
+ while (existingIds.has(`${baseId}-${counter}`)) counter++
1025
+ return `${baseId}-${counter}`
1026
+ }
1027
+
1028
+ interface LabelItem {
1029
+ creatable?: string
1030
+ id: string
1031
+ value: string
1032
+ }
1033
+
1034
+ const initialLabels: LabelItem[] = [
1035
+ { id: 'bug', value: 'bug' },
1036
+ { id: 'docs', value: 'documentation' },
1037
+ { id: 'enhancement', value: 'enhancement' },
1038
+ { id: 'help-wanted', value: 'help wanted' },
1039
+ { id: 'good-first-issue', value: 'good first issue' },
1040
+ ]
1041
+
1042
+ export default function CreatableExample() {
1043
+ const [labels, setLabels] = useState<LabelItem[]>(initialLabels)
1044
+ const [selected, setSelected] = useState<LabelItem[]>([])
1045
+ const [query, setQuery] = useState('')
1046
+ const [openDialog, setOpenDialog] = useState(false)
1047
+ const [createValue, setCreateValue] = useState('')
1048
+
1049
+ const containerRef = useRef<HTMLDivElement>(null)
1050
+ const createInputRef = useRef<HTMLInputElement>(null)
1051
+ const comboboxInputRef = useRef<HTMLInputElement>(null)
1052
+ const highlightedItemRef = useRef<LabelItem>(undefined)
1053
+
1054
+ function handleInputKeyDown(event: KeyboardEvent<HTMLInputElement>) {
1055
+ if (event.key !== 'Enter' || highlightedItemRef.current) {
1056
+ return
1057
+ }
1058
+
1059
+ const currentTrimmed = query.trim()
1060
+ if (currentTrimmed === '') {
1061
+ return
1062
+ }
1063
+
1064
+ const normalized = normalize(currentTrimmed)
1065
+ const existing = labels.find((label) => normalize(label.value) === normalized)
1066
+
1067
+ if (existing) {
1068
+ setSelected((prev) =>
1069
+ prev.some((item) => item.id === existing.id) ? prev : [...prev, existing],
1070
+ )
1071
+ setQuery('')
1072
+ return
1073
+ }
1074
+
1075
+ setCreateValue(currentTrimmed)
1076
+ setOpenDialog(true)
1077
+ }
1078
+
1079
+ function handleCreate() {
1080
+ const value = createValue.trim() || createInputRef.current?.value.trim() || ''
1081
+ if (!value) {
1082
+ return
1083
+ }
1084
+
1085
+ const normalized = normalize(value)
1086
+ const baseId = normalized.replace(/\s+/g, '-')
1087
+ const existing = labels.find((label) => normalize(label.value) === normalized)
1088
+
1089
+ if (existing) {
1090
+ setSelected((prev) =>
1091
+ prev.some((item) => item.id === existing.id) ? prev : [...prev, existing],
1092
+ )
1093
+ setOpenDialog(false)
1094
+ setQuery('')
1095
+ return
1096
+ }
1097
+
1098
+ const existingIds = new Set(labels.map((label) => label.id))
1099
+ const uniqueId = generateUniqueId(baseId, existingIds)
1100
+ const newItem: LabelItem = { id: uniqueId, value }
1101
+
1102
+ if (!selected.find((item) => item.id === newItem.id)) {
1103
+ setLabels((prev) => [...prev, newItem])
1104
+ setSelected((prev) => [...prev, newItem])
1105
+ }
1106
+
1107
+ setOpenDialog(false)
1108
+ setQuery('')
1109
+ }
1110
+
1111
+ function handleCreateSubmit(event: FormEvent<HTMLFormElement>) {
1112
+ event.preventDefault()
1113
+ handleCreate()
1114
+ }
1115
+
1116
+ const itemsForView = useMemo(() => {
1117
+ const trimmed = query.trim()
1118
+ const lowered = normalize(trimmed)
1119
+ const exactExists = labels.some((label) => normalize(label.value) === lowered)
1120
+
1121
+ if (trimmed !== '' && !exactExists) {
1122
+ return [
1123
+ ...labels,
1124
+ {
1125
+ creatable: trimmed,
1126
+ id: `create:${lowered}`,
1127
+ value: `Create "${trimmed}"`,
1128
+ },
1129
+ ]
1130
+ }
1131
+ return labels
1132
+ }, [query, labels])
1133
+
1134
+ return (
1135
+ <>
1136
+ <FieldRoot>
1137
+ <FieldLabel>Labels</FieldLabel>
1138
+ <ComboboxRoot
1139
+ items={itemsForView}
1140
+ multiple
1141
+ onValueChange={(next) => {
1142
+ const creatableSelection = next.find(
1143
+ (item) =>
1144
+ item.creatable && !selected.some((current) => current.id === item.id),
1145
+ )
1146
+
1147
+ if (creatableSelection && creatableSelection.creatable) {
1148
+ setCreateValue(creatableSelection.creatable)
1149
+ setOpenDialog(true)
1150
+ return
1151
+ }
1152
+ const clean = next.filter((item) => !item.creatable)
1153
+ setSelected(clean)
1154
+ setQuery('')
1155
+ }}
1156
+ value={selected}
1157
+ inputValue={query}
1158
+ onInputValueChange={setQuery}
1159
+ onItemHighlighted={(item) => {
1160
+ highlightedItemRef.current = item
1161
+ }}
1162
+ >
1163
+ <ComboboxChips ref={containerRef} className='max-w-xs'>
1164
+ <ComboboxValue>
1165
+ {(value: LabelItem[]) => (
1166
+ <>
1167
+ {value.map((label) => (
1168
+ <ComboboxChip key={label.id} aria-label={label.value}>
1169
+ {label.value}
1170
+ <ComboboxChipRemove aria-label='Remove'>
1171
+ <X className='size-3' />
1172
+ </ComboboxChipRemove>
1173
+ </ComboboxChip>
1174
+ ))}
1175
+ <ComboboxInput
1176
+ ref={comboboxInputRef}
1177
+ placeholder={value.length > 0 ? '' : 'e.g. bug'}
1178
+ onKeyDown={handleInputKeyDown}
1179
+ />
1180
+ </>
1181
+ )}
1182
+ </ComboboxValue>
1183
+ </ComboboxChips>
1184
+ <ComboboxPortal>
1185
+ <ComboboxPositioner sideOffset={4} anchor={containerRef}>
1186
+ <ComboboxPopup>
1187
+ <ComboboxEmpty>No labels found.</ComboboxEmpty>
1188
+ <ComboboxList>
1189
+ {(item: LabelItem) =>
1190
+ item.creatable ? (
1191
+ <ComboboxItem key={item.id} value={item}>
1192
+ <span className='flex items-center gap-2'>
1193
+ <Plus className='size-3' />
1194
+ <ComboboxItemText>Create {item.creatable}</ComboboxItemText>
1195
+ </span>
1196
+ </ComboboxItem>
1197
+ ) : (
1198
+ <ComboboxItem key={item.id} value={item}>
1199
+ <ComboboxItemText>{item.value}</ComboboxItemText>
1200
+ <ComboboxItemIndicator>
1201
+ <Check className='size-3.5' />
1202
+ </ComboboxItemIndicator>
1203
+ </ComboboxItem>
1204
+ )
1205
+ }
1206
+ </ComboboxList>
1207
+ </ComboboxPopup>
1208
+ </ComboboxPositioner>
1209
+ </ComboboxPortal>
1210
+ </ComboboxRoot>
1211
+ </FieldRoot>
1212
+
1213
+ <DialogRoot
1214
+ open={openDialog}
1215
+ onOpenChange={(open) => {
1216
+ setOpenDialog(open)
1217
+ if (!open) setCreateValue('')
1218
+ }}
1219
+ >
1220
+ <DialogPortal>
1221
+ <DialogBackdrop />
1222
+ <DialogPopup size='sm' initialFocus={createInputRef}>
1223
+ <DialogHeader>
1224
+ <DialogTitle>Create new label</DialogTitle>
1225
+ <DialogDescription>Add a new label to select.</DialogDescription>
1226
+ </DialogHeader>
1227
+ <FormRoot onSubmit={handleCreateSubmit}>
1228
+ <FieldRoot name='labelName'>
1229
+ <FieldLabel>Label name</FieldLabel>
1230
+ <FieldControl
1231
+ ref={createInputRef}
1232
+ placeholder='Label name'
1233
+ value={createValue}
1234
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
1235
+ setCreateValue(e.target.value)
1236
+ }
1237
+ />
1238
+ </FieldRoot>
1239
+ <DialogFooter>
1240
+ <DialogClose variant='ghost'>Cancel</DialogClose>
1241
+ <Button type='submit'>Create</Button>
1242
+ </DialogFooter>
1243
+ </FormRoot>
1244
+ </DialogPopup>
1245
+ </DialogPortal>
1246
+ </DialogRoot>
1247
+ </>
1248
+ )
1249
+ }
1250
+ ```
1251
+
1252
+ ## Resources
1253
+
1254
+ - [Base UI Combobox Documentation](https://base-ui.com/react/components/combobox)
1255
+ - [API Reference](https://base-ui.com/react/components/combobox#api-reference)