@sproutsocial/seeds-react-menu 1.10.8 → 1.11.0

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 (50) hide show
  1. package/.turbo/turbo-build.log +21 -15
  2. package/CHANGELOG.md +22 -0
  3. package/dist/esm/index.js +45 -24
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/v2/index.js +1 -1
  6. package/dist/esm/v2/index.js.map +1 -1
  7. package/dist/esm/v3/index.js +800 -0
  8. package/dist/esm/v3/index.js.map +1 -0
  9. package/dist/index.d.mts +22 -22
  10. package/dist/index.d.ts +22 -22
  11. package/dist/index.js +35 -14
  12. package/dist/index.js.map +1 -1
  13. package/dist/v2/index.js +1 -1
  14. package/dist/v2/index.js.map +1 -1
  15. package/dist/v3/index.d.mts +295 -0
  16. package/dist/v3/index.d.ts +295 -0
  17. package/dist/v3/index.js +868 -0
  18. package/dist/v3/index.js.map +1 -0
  19. package/package.json +7 -1
  20. package/src/MenuGroup/MenuGroup.tsx +16 -3
  21. package/src/MenuGroup/__tests__/MenuGroup.test.tsx +19 -0
  22. package/src/MenuItem/MenuItem.tsx +21 -10
  23. package/src/MenuItem/__tests__/MenuItem.test.tsx +19 -0
  24. package/src/v2/Common-V2/ComboboxContent.tsx +1 -1
  25. package/src/v3/Common/ComboboxItem.tsx +82 -0
  26. package/src/v3/Common/ComboboxStyles.tsx +475 -0
  27. package/src/v3/Common/SelectGroup.tsx +28 -0
  28. package/src/v3/Common/SelectIcons.tsx +26 -0
  29. package/src/v3/Common/SelectItem.tsx +108 -0
  30. package/src/v3/Common/SelectStyles.tsx +126 -0
  31. package/src/v3/Common/VirtualizedComboboxList.tsx +200 -0
  32. package/src/v3/Common/groupItems.ts +12 -0
  33. package/src/v3/Common/types.ts +3 -0
  34. package/src/v3/ModalStories.stories.tsx +181 -0
  35. package/src/v3/MultiCombobox.stories.tsx +322 -0
  36. package/src/v3/MultiCombobox.tsx +562 -0
  37. package/src/v3/MultiSearchableSelect.stories.tsx +389 -0
  38. package/src/v3/MultiSearchableSelect.tsx +545 -0
  39. package/src/v3/MultiSelect.stories.tsx +300 -0
  40. package/src/v3/MultiSelect.tsx +498 -0
  41. package/src/v3/SeedsPortal.tsx +35 -0
  42. package/src/v3/SingleCombobox.stories.tsx +169 -0
  43. package/src/v3/SingleCombobox.tsx +304 -0
  44. package/src/v3/SingleSearchableSelect.stories.tsx +239 -0
  45. package/src/v3/SingleSearchableSelect.tsx +319 -0
  46. package/src/v3/SingleSelect.stories.tsx +227 -0
  47. package/src/v3/SingleSelect.tsx +245 -0
  48. package/src/v3/index.ts +7 -0
  49. package/src/v3/storyData.tsx +117 -0
  50. package/tsup.config.ts +1 -0
@@ -0,0 +1,562 @@
1
+ import * as React from "react";
2
+ import { Combobox } from "@base-ui/react/combobox";
3
+ import { useVirtualizer } from "@tanstack/react-virtual";
4
+ import VirtualizedComboboxList from "./Common/VirtualizedComboboxList";
5
+ import { useSeedsPortalContainer } from "./SeedsPortal";
6
+ import ComboboxItem from "./Common/ComboboxItem";
7
+ import type { DataWithId } from "./Common/types";
8
+ import { groupItems } from "./Common/groupItems";
9
+ import { Field } from "./Common/SelectStyles";
10
+ import {
11
+ ComboboxTokenInputGroup,
12
+ ComboboxTokenInput,
13
+ ComboboxToken,
14
+ ComboboxActionButtons,
15
+ ComboboxClear,
16
+ ComboboxTrigger,
17
+ ComboboxPositioner,
18
+ ComboboxPopup,
19
+ ComboboxList,
20
+ ComboboxEmpty,
21
+ ComboboxStatus,
22
+ ComboboxGroup,
23
+ ComboboxGroupLabel,
24
+ ComboboxSeparator,
25
+ Token,
26
+ TokenRemove,
27
+ ComboboxGroupHeaderItem,
28
+ ComboboxGroupHeaderCheckbox,
29
+ ComboboxSelectAllItem,
30
+ } from "./Common/ComboboxStyles";
31
+ import { ClearIcon, StyledChevron, ChevronIcon } from "./Common/SelectIcons";
32
+ import Icon from "@sproutsocial/seeds-react-icon";
33
+
34
+ // ─── Sentinels ────────────────────────────────────────────────────────────────
35
+
36
+ interface GroupHeaderSentinel {
37
+ __groupHeader: true;
38
+ groupName: string;
39
+ }
40
+
41
+ function makeGroupHeaderSentinel(groupName: string): GroupHeaderSentinel {
42
+ return { __groupHeader: true, groupName };
43
+ }
44
+
45
+ function isGroupHeaderSentinel(v: unknown): v is GroupHeaderSentinel {
46
+ return typeof v === "object" && v !== null && "__groupHeader" in v;
47
+ }
48
+
49
+ interface SelectAllSentinel {
50
+ __selectAll: true;
51
+ }
52
+
53
+ const SELECT_ALL_SENTINEL: SelectAllSentinel = { __selectAll: true };
54
+
55
+ function isSelectAllSentinel(v: unknown): v is SelectAllSentinel {
56
+ return typeof v === "object" && v !== null && "__selectAll" in v;
57
+ }
58
+
59
+ // ─── Props ────────────────────────────────────────────────────────────────────
60
+
61
+ interface MultiComboboxBaseProps {
62
+ id?: string;
63
+ placeholder?: string;
64
+ emptyText?: string;
65
+ isLoading?: boolean;
66
+ loadingText?: string;
67
+ }
68
+
69
+ interface MultiComboboxDataProps<T extends DataWithId>
70
+ extends MultiComboboxBaseProps {
71
+ data: T[];
72
+ itemToString: (item: T) => string;
73
+ renderItem?: (item: T) => React.ReactNode;
74
+ renderToken?: (item: T) => React.ReactNode;
75
+ renderSelection?: (items: T[]) => React.ReactNode;
76
+ inputType?: "icon" | "checkbox" | "none";
77
+ selectedItemIds?: Array<T["id"]>;
78
+ defaultSelectedItemIds?: Array<T["id"]>;
79
+ onSelectedItemIdsChange?: (ids: Array<T["id"]>) => void;
80
+ groupBy?: (item: T) => string;
81
+ renderGroupHeading?: (label: string) => React.ReactNode;
82
+ selectHeadings?: boolean;
83
+ selectAll?: boolean;
84
+ isVirtualized?: boolean;
85
+ removeSelectedItems?: boolean;
86
+ maxTokens?: number;
87
+ children?: never;
88
+ }
89
+
90
+ interface MultiComboboxChildrenProps extends MultiComboboxBaseProps {
91
+ children: React.ReactNode;
92
+ selectedItemIds?: Array<string | number>;
93
+ defaultSelectedItemIds?: Array<string | number>;
94
+ onSelectedItemIdsChange?: (ids: Array<string | number>) => void;
95
+ data?: never;
96
+ itemToString?: never;
97
+ renderItem?: never;
98
+ renderToken?: never;
99
+ renderSelection?: never;
100
+ inputType?: never;
101
+ groupBy?: never;
102
+ renderGroupHeading?: never;
103
+ selectHeadings?: never;
104
+ selectAll?: never;
105
+ removeSelectedItems?: never;
106
+ }
107
+
108
+ export type MultiComboboxProps<T extends DataWithId> =
109
+ | MultiComboboxDataProps<T>
110
+ | MultiComboboxChildrenProps;
111
+
112
+ // ─── Component ────────────────────────────────────────────────────────────────
113
+
114
+ // ─── Virtualized list ─────────────────────────────────────────────────────────
115
+
116
+ // ─── Component ────────────────────────────────────────────────────────────────
117
+
118
+ export default function MultiCombobox<T extends DataWithId>(
119
+ props: MultiComboboxProps<T>
120
+ ) {
121
+ const {
122
+ id,
123
+ placeholder = "Search...",
124
+ emptyText = "No results found.",
125
+ isLoading = false,
126
+ loadingText = "Loading...",
127
+ } = props;
128
+
129
+ const isChildrenMode = "children" in props && props.children != null;
130
+
131
+ const data = isChildrenMode
132
+ ? ([] as T[])
133
+ : (props as MultiComboboxDataProps<T>).data;
134
+ const itemToString = isChildrenMode
135
+ ? (_item: T) => ""
136
+ : (props as MultiComboboxDataProps<T>).itemToString;
137
+ const renderItem = isChildrenMode
138
+ ? undefined
139
+ : (props as MultiComboboxDataProps<T>).renderItem;
140
+ const renderToken = isChildrenMode
141
+ ? undefined
142
+ : (props as MultiComboboxDataProps<T>).renderToken;
143
+ const renderSelection = isChildrenMode
144
+ ? undefined
145
+ : (props as MultiComboboxDataProps<T>).renderSelection;
146
+ const maxTokens = isChildrenMode
147
+ ? undefined
148
+ : (props as MultiComboboxDataProps<T>).maxTokens;
149
+ const removeSelectedItems = isChildrenMode
150
+ ? false
151
+ : (props as MultiComboboxDataProps<T>).removeSelectedItems ?? false;
152
+ const inputType = isChildrenMode
153
+ ? "checkbox"
154
+ : removeSelectedItems
155
+ ? "none"
156
+ : (props as MultiComboboxDataProps<T>).inputType ?? "checkbox";
157
+ const groupBy = isChildrenMode
158
+ ? undefined
159
+ : (props as MultiComboboxDataProps<T>).groupBy;
160
+ const renderGroupHeading = isChildrenMode
161
+ ? undefined
162
+ : (props as MultiComboboxDataProps<T>).renderGroupHeading;
163
+ const selectHeadings = isChildrenMode
164
+ ? false
165
+ : (props as MultiComboboxDataProps<T>).selectHeadings ?? false;
166
+ const selectAll = isChildrenMode
167
+ ? false
168
+ : (props as MultiComboboxDataProps<T>).selectAll ?? false;
169
+ const isVirtualized = isChildrenMode
170
+ ? false
171
+ : (props as MultiComboboxDataProps<T>).isVirtualized ?? false;
172
+ const children = isChildrenMode
173
+ ? (props as MultiComboboxChildrenProps).children
174
+ : undefined;
175
+
176
+ const selectedItemIds = props.selectedItemIds;
177
+ const defaultSelectedItemIds = props.defaultSelectedItemIds;
178
+ const onSelectedItemIdsChange = props.onSelectedItemIdsChange;
179
+
180
+ const { containerRef, portalContainer } = useSeedsPortalContainer();
181
+ const [open, setOpen] = React.useState(false);
182
+ const virtualizerRef = React.useRef<ReturnType<
183
+ typeof useVirtualizer<HTMLDivElement, Element>
184
+ > | null>(null);
185
+
186
+ const isControlled = selectedItemIds !== undefined;
187
+
188
+ const idsToItems = React.useCallback(
189
+ (ids: Array<T["id"]>): T[] =>
190
+ ids
191
+ .map((id) => data.find((d) => d.id === id))
192
+ .filter((d): d is T => d != null),
193
+ [data]
194
+ );
195
+
196
+ const [internalValue, setInternalValue] = React.useState<T[]>(() =>
197
+ defaultSelectedItemIds
198
+ ? idsToItems(defaultSelectedItemIds as Array<T["id"]>)
199
+ : []
200
+ );
201
+
202
+ const value = isControlled
203
+ ? idsToItems(selectedItemIds as Array<T["id"]>)
204
+ : internalValue;
205
+
206
+ const visibleData = React.useMemo(
207
+ () =>
208
+ removeSelectedItems
209
+ ? data.filter((item) => !value.some((v) => v.id === item.id))
210
+ : data,
211
+ [data, value, removeSelectedItems]
212
+ );
213
+
214
+ const groups = React.useMemo(
215
+ () => (groupBy ? groupItems(visibleData, groupBy) : null),
216
+ [visibleData, groupBy]
217
+ );
218
+
219
+ // ─── Sentinel flat list ───────────────────────────────────────────────────
220
+ // Used when selectHeadings or selectAll. selectAll implies selectHeadings —
221
+ // group headers are always selectable when the list is flattened.
222
+
223
+ type FlatRow = T | GroupHeaderSentinel | SelectAllSentinel;
224
+
225
+ const flatItems = React.useMemo<FlatRow[] | null>(() => {
226
+ if (!groups || (!selectHeadings && !selectAll)) return null;
227
+ const rows: FlatRow[] = [];
228
+ if (selectAll) rows.push(SELECT_ALL_SENTINEL);
229
+ for (const group of groups) {
230
+ rows.push(makeGroupHeaderSentinel(group.value));
231
+ rows.push(...group.items);
232
+ }
233
+ return rows;
234
+ }, [groups, selectHeadings, selectAll]);
235
+
236
+ function setSelectedItems(items: T[]) {
237
+ if (!isControlled) setInternalValue(items);
238
+ if (onSelectedItemIdsChange)
239
+ onSelectedItemIdsChange(items.map((item) => item.id));
240
+ }
241
+
242
+ function handleValueChange(next: FlatRow[]) {
243
+ const selectAllClicked = next.filter(isSelectAllSentinel);
244
+ const groupSentinels = next.filter(isGroupHeaderSentinel);
245
+ const realItems = next.filter(
246
+ (v): v is T => !isGroupHeaderSentinel(v) && !isSelectAllSentinel(v)
247
+ );
248
+
249
+ if (selectAllClicked.length > 0) {
250
+ const allSelected = data.every((d) =>
251
+ realItems.some((s) => s.id === d.id)
252
+ );
253
+ setSelectedItems(allSelected ? [] : [...data]);
254
+ return;
255
+ }
256
+
257
+ if (groupSentinels.length === 0) {
258
+ setSelectedItems(realItems);
259
+ return;
260
+ }
261
+
262
+ let updated = [...realItems];
263
+ for (const sentinel of groupSentinels) {
264
+ const group = groups?.find((g) => g.value === sentinel.groupName);
265
+ if (!group) continue;
266
+ const allSelected = group.items.every((item) =>
267
+ updated.some((s) => s.id === item.id)
268
+ );
269
+ if (allSelected) {
270
+ updated = updated.filter(
271
+ (s) => !group.items.some((item) => item.id === s.id)
272
+ );
273
+ } else {
274
+ const missing = group.items.filter(
275
+ (item) => !updated.some((s) => s.id === item.id)
276
+ );
277
+ updated = [...updated, ...missing];
278
+ }
279
+ }
280
+ setSelectedItems(updated);
281
+ }
282
+
283
+ function handleSimpleValueChange(newItems: T[]) {
284
+ setSelectedItems(newItems);
285
+ }
286
+
287
+ function removeItem(item: T, e: React.MouseEvent) {
288
+ e.preventDefault();
289
+ e.stopPropagation();
290
+ setSelectedItems(value.filter((v) => v.id !== item.id));
291
+ }
292
+
293
+ // ─── Render sentinel item ─────────────────────────────────────────────────
294
+
295
+ function renderSentinelRow(row: FlatRow) {
296
+ if (isSelectAllSentinel(row)) {
297
+ const totalCount = data.length;
298
+ const selectedCount = value.length;
299
+ const state: "none" | "partial" | "all" =
300
+ selectedCount === 0
301
+ ? "none"
302
+ : selectedCount === totalCount
303
+ ? "all"
304
+ : "partial";
305
+ return (
306
+ <ComboboxSelectAllItem key="__selectAll" value={row}>
307
+ <ComboboxGroupHeaderCheckbox $state={state} id="__selectAll" />
308
+ Select all
309
+ </ComboboxSelectAllItem>
310
+ );
311
+ }
312
+
313
+ if (isGroupHeaderSentinel(row)) {
314
+ const group = groups?.find((g) => g.value === row.groupName);
315
+ const groupItemsList = group?.items ?? [];
316
+ const selectedCount = groupItemsList.filter((item) =>
317
+ value.some((s) => s.id === item.id)
318
+ ).length;
319
+ const state: "none" | "partial" | "all" =
320
+ selectedCount === 0
321
+ ? "none"
322
+ : selectedCount === groupItemsList.length
323
+ ? "all"
324
+ : "partial";
325
+ return (
326
+ <ComboboxGroupHeaderItem key={`__header-${row.groupName}`} value={row}>
327
+ <ComboboxGroupHeaderCheckbox
328
+ $state={state}
329
+ id={`__header-${row.groupName}`}
330
+ />
331
+ {renderGroupHeading
332
+ ? renderGroupHeading(row.groupName)
333
+ : row.groupName}
334
+ </ComboboxGroupHeaderItem>
335
+ );
336
+ }
337
+
338
+ const item = row as T;
339
+ return (
340
+ <ComboboxItem
341
+ key={item.id}
342
+ value={item}
343
+ itemId={String(item.id)}
344
+ label={itemToString(item)}
345
+ inputType={inputType}
346
+ renderItem={
347
+ renderItem ? () => renderItem(item) : () => itemToString(item)
348
+ }
349
+ />
350
+ );
351
+ }
352
+
353
+ // ─── Items passed to Combobox.Root ────────────────────────────────────────
354
+ // When selectHeadings: flatItems (flat array of group header sentinels + items).
355
+ // When selectAll only: prepend SELECT_ALL_SENTINEL to data so Base UI tracks
356
+ // it, while Combobox.List still uses the normal grouped render.
357
+ // The sentinel is never in flatItems — it is always rendered manually above
358
+ // Combobox.List, so we must not prepend it when flatItems is used.
359
+
360
+ const rootItems =
361
+ flatItems ?? groups ?? (isChildrenMode ? undefined : visibleData);
362
+
363
+ const hasSentinels = selectAll || selectHeadings;
364
+
365
+ const rootValue = hasSentinels
366
+ ? ([...value] as FlatRow[])
367
+ : (value as unknown as T[]);
368
+
369
+ return (
370
+ <Field>
371
+ <div ref={containerRef} style={{ position: "relative" }} />
372
+ <Combobox.Root
373
+ multiple
374
+ id={id}
375
+ virtualized={isVirtualized}
376
+ open={open}
377
+ onOpenChange={setOpen}
378
+ onItemHighlighted={
379
+ isVirtualized
380
+ ? (item, { reason, index }) => {
381
+ const virt = virtualizerRef.current;
382
+ if (!item || !virt) return;
383
+ const isStart = index === 0;
384
+ const isEnd = index === virt.options.count - 1;
385
+ if (
386
+ reason === "none" ||
387
+ (reason === "keyboard" && (isStart || isEnd))
388
+ ) {
389
+ queueMicrotask(() => {
390
+ virt.scrollToIndex(index, {
391
+ align: isEnd ? "start" : "end",
392
+ });
393
+ });
394
+ }
395
+ }
396
+ : undefined
397
+ }
398
+ value={rootValue}
399
+ onValueChange={
400
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
401
+ (hasSentinels ? handleValueChange : handleSimpleValueChange) as any
402
+ }
403
+ itemToStringLabel={(item: FlatRow | null) => {
404
+ if (!item || isGroupHeaderSentinel(item) || isSelectAllSentinel(item))
405
+ return "";
406
+ return itemToString(item as T);
407
+ }}
408
+ isItemEqualToValue={(a: FlatRow, b: FlatRow) => {
409
+ if (isGroupHeaderSentinel(a) && isGroupHeaderSentinel(b))
410
+ return a.groupName === b.groupName;
411
+ if (isSelectAllSentinel(a) && isSelectAllSentinel(b)) return true;
412
+ if (
413
+ !isGroupHeaderSentinel(a) &&
414
+ !isSelectAllSentinel(a) &&
415
+ !isGroupHeaderSentinel(b) &&
416
+ !isSelectAllSentinel(b)
417
+ )
418
+ return (a as T).id === (b as T).id;
419
+ return false;
420
+ }}
421
+ items={rootItems}
422
+ >
423
+ <ComboboxTokenInputGroup>
424
+ <ComboboxToken>
425
+ {renderSelection
426
+ ? renderSelection(value)
427
+ : (() => {
428
+ const visibleTokens =
429
+ maxTokens != null ? value.slice(0, maxTokens) : value;
430
+ const overflow =
431
+ maxTokens != null ? value.length - maxTokens : 0;
432
+ return (
433
+ <>
434
+ {visibleTokens.map((item) => (
435
+ <Token key={item.id}>
436
+ {renderToken ? renderToken(item) : itemToString(item)}
437
+ <TokenRemove
438
+ aria-label={`Remove ${itemToString(item)}`}
439
+ onPointerDown={(e) => e.preventDefault()}
440
+ onClick={(e) => removeItem(item, e)}
441
+ >
442
+ <Icon name="x-outline" size="mini" />
443
+ </TokenRemove>
444
+ </Token>
445
+ ))}
446
+ {overflow > 0 && (
447
+ <Token key="__overflow">+{overflow}</Token>
448
+ )}
449
+ </>
450
+ );
451
+ })()}
452
+ <ComboboxTokenInput
453
+ id={id}
454
+ placeholder={value.length > 0 ? "" : placeholder}
455
+ />
456
+ <ComboboxActionButtons>
457
+ <ComboboxClear aria-label="Clear all">
458
+ <ClearIcon />
459
+ </ComboboxClear>
460
+ <ComboboxTrigger aria-label="Open popup">
461
+ <StyledChevron $isOpen={open}>
462
+ <ChevronIcon />
463
+ </StyledChevron>
464
+ </ComboboxTrigger>
465
+ </ComboboxActionButtons>
466
+ </ComboboxToken>
467
+ </ComboboxTokenInputGroup>
468
+
469
+ <Combobox.Portal container={portalContainer}>
470
+ <ComboboxPositioner sideOffset={4}>
471
+ <ComboboxPopup
472
+ aria-busy={isLoading || undefined}
473
+ style={
474
+ isVirtualized
475
+ ? { display: "flex", flexDirection: "column" }
476
+ : undefined
477
+ }
478
+ >
479
+ <ComboboxStatus>{isLoading ? loadingText : null}</ComboboxStatus>
480
+ <ComboboxEmpty>{isLoading ? null : emptyText}</ComboboxEmpty>
481
+ <ComboboxList
482
+ style={
483
+ isVirtualized
484
+ ? {
485
+ padding: 0,
486
+ flex: 1,
487
+ minHeight: 0,
488
+ display: "flex",
489
+ flexDirection: "column",
490
+ }
491
+ : undefined
492
+ }
493
+ >
494
+ {isVirtualized ? (
495
+ <VirtualizedComboboxList
496
+ open={open}
497
+ virtualizerRef={virtualizerRef}
498
+ value={value}
499
+ groups={groups}
500
+ data={visibleData}
501
+ itemToString={itemToString}
502
+ inputType={inputType}
503
+ renderItem={renderItem}
504
+ renderGroupHeading={renderGroupHeading}
505
+ />
506
+ ) : children ? (
507
+ children
508
+ ) : flatItems ? (
509
+ (row: FlatRow) => renderSentinelRow(row)
510
+ ) : groups ? (
511
+ (group: { value: string; items: T[] }, index: number) => (
512
+ <React.Fragment key={group.value}>
513
+ <ComboboxGroup items={group.items}>
514
+ <ComboboxGroupLabel>
515
+ {renderGroupHeading
516
+ ? renderGroupHeading(group.value)
517
+ : group.value}
518
+ </ComboboxGroupLabel>
519
+ <Combobox.Collection>
520
+ {(item: T) => (
521
+ <ComboboxItem
522
+ key={item.id}
523
+ value={item}
524
+ itemId={String(item.id)}
525
+ label={itemToString(item)}
526
+ inputType={inputType}
527
+ renderItem={
528
+ renderItem
529
+ ? () => renderItem(item)
530
+ : () => itemToString(item)
531
+ }
532
+ />
533
+ )}
534
+ </Combobox.Collection>
535
+ </ComboboxGroup>
536
+ {index < groups.length - 1 && <ComboboxSeparator />}
537
+ </React.Fragment>
538
+ )
539
+ ) : (
540
+ (item: T) => (
541
+ <ComboboxItem
542
+ key={item.id}
543
+ value={item}
544
+ itemId={String(item.id)}
545
+ label={itemToString(item)}
546
+ inputType={inputType}
547
+ renderItem={
548
+ renderItem
549
+ ? () => renderItem(item)
550
+ : () => itemToString(item)
551
+ }
552
+ />
553
+ )
554
+ )}
555
+ </ComboboxList>
556
+ </ComboboxPopup>
557
+ </ComboboxPositioner>
558
+ </Combobox.Portal>
559
+ </Combobox.Root>
560
+ </Field>
561
+ );
562
+ }