@sproutsocial/seeds-react-menu 1.12.0 → 1.12.2

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.
@@ -6,6 +6,11 @@ import { useSeedsPortalContainer } from "./SeedsPortal";
6
6
  import ComboboxItem from "./Common/ComboboxItem";
7
7
  import type { DataWithId } from "./Common/types";
8
8
  import { groupItems } from "./Common/groupItems";
9
+ import {
10
+ isCreatableSentinel,
11
+ makeCreatableSentinel,
12
+ type CreatableSentinel,
13
+ } from "./Common/sentinels";
9
14
  import { Field } from "./Common/SelectStyles";
10
15
  import {
11
16
  ComboboxTokenInputGroup,
@@ -66,6 +71,9 @@ interface MultiComboboxBaseProps {
66
71
  loadingText?: string;
67
72
  disabled?: boolean;
68
73
  filter?: ((item: unknown, query: string) => boolean) | null;
74
+ inputValue?: string;
75
+ onInputValueChange?: (value: string) => void;
76
+ "aria-describedby"?: string;
69
77
  }
70
78
 
71
79
  interface MultiComboboxDataProps<T extends DataWithId>
@@ -82,10 +90,12 @@ interface MultiComboboxDataProps<T extends DataWithId>
82
90
  groupBy?: (item: T) => string;
83
91
  renderGroupHeading?: (label: string) => React.ReactNode;
84
92
  selectHeadings?: boolean;
85
- selectAll?: boolean;
93
+ selectAll?: boolean | string;
86
94
  isVirtualized?: boolean;
87
95
  removeSelectedItems?: boolean;
88
96
  maxTokens?: number;
97
+ creatable?: boolean;
98
+ onCreate?: (value: string) => T;
89
99
  children?: never;
90
100
  }
91
101
 
@@ -105,6 +115,8 @@ interface MultiComboboxChildrenProps extends MultiComboboxBaseProps {
105
115
  selectHeadings?: never;
106
116
  selectAll?: never;
107
117
  removeSelectedItems?: never;
118
+ creatable?: never;
119
+ onCreate?: never;
108
120
  }
109
121
 
110
122
  export type MultiComboboxProps<T extends DataWithId> =
@@ -127,7 +139,10 @@ export function MultiCombobox<T extends DataWithId>(
127
139
  isLoading = false,
128
140
  loadingText = "Loading...",
129
141
  filter,
142
+ inputValue,
143
+ onInputValueChange,
130
144
  } = props;
145
+ const ariaDescribedBy = props["aria-describedby"];
131
146
 
132
147
  const isChildrenMode = "children" in props && props.children != null;
133
148
 
@@ -166,12 +181,21 @@ export function MultiCombobox<T extends DataWithId>(
166
181
  const selectHeadings = isChildrenMode
167
182
  ? false
168
183
  : (props as MultiComboboxDataProps<T>).selectHeadings ?? false;
169
- const selectAll = isChildrenMode
184
+ const selectAllProp = isChildrenMode
170
185
  ? false
171
186
  : (props as MultiComboboxDataProps<T>).selectAll ?? false;
187
+ const selectAll = Boolean(selectAllProp);
188
+ const selectAllLabel =
189
+ typeof selectAllProp === "string" ? selectAllProp : "Select all";
172
190
  const isVirtualized = isChildrenMode
173
191
  ? false
174
192
  : (props as MultiComboboxDataProps<T>).isVirtualized ?? false;
193
+ const creatableProp = isChildrenMode
194
+ ? false
195
+ : (props as MultiComboboxDataProps<T>).creatable ?? false;
196
+ const onCreateProp = isChildrenMode
197
+ ? undefined
198
+ : (props as MultiComboboxDataProps<T>).onCreate;
175
199
  const children = isChildrenMode
176
200
  ? (props as MultiComboboxChildrenProps).children
177
201
  : undefined;
@@ -182,6 +206,8 @@ export function MultiCombobox<T extends DataWithId>(
182
206
 
183
207
  const { containerRef, portalContainer } = useSeedsPortalContainer();
184
208
  const [open, setOpen] = React.useState(false);
209
+ const [internalInputValue, setInternalInputValue] = React.useState("");
210
+ const skipNextInputChangeRef = React.useRef(false);
185
211
  const virtualizerRef = React.useRef<ReturnType<
186
212
  typeof useVirtualizer<HTMLDivElement, Element>
187
213
  > | null>(null);
@@ -219,22 +245,45 @@ export function MultiCombobox<T extends DataWithId>(
219
245
  [visibleData, groupBy]
220
246
  );
221
247
 
248
+ const currentInputValue = inputValue ?? internalInputValue;
249
+
250
+ const creatableSentinel = React.useMemo<CreatableSentinel | null>(() => {
251
+ if (!creatableProp) return null;
252
+ const trimmed = currentInputValue.trim();
253
+ if (!trimmed) return null;
254
+ const lowered = trimmed.toLocaleLowerCase();
255
+ const exactExists = data.some(
256
+ (d) => itemToString(d).trim().toLocaleLowerCase() === lowered
257
+ );
258
+ if (exactExists) return null;
259
+ return makeCreatableSentinel(trimmed);
260
+ }, [creatableProp, currentInputValue, data, itemToString]);
261
+
222
262
  // ─── Sentinel flat list ───────────────────────────────────────────────────
223
263
  // Used when selectHeadings or selectAll. selectAll implies selectHeadings —
224
264
  // group headers are always selectable when the list is flattened.
225
265
 
226
- type FlatRow = T | GroupHeaderSentinel | SelectAllSentinel;
266
+ type FlatRow =
267
+ | T
268
+ | GroupHeaderSentinel
269
+ | SelectAllSentinel
270
+ | CreatableSentinel;
227
271
 
228
272
  const flatItems = React.useMemo<FlatRow[] | null>(() => {
229
- if (!groups || (!selectHeadings && !selectAll)) return null;
273
+ if (!selectHeadings && !selectAll) return null;
274
+ if (!groups && !selectAll) return null;
230
275
  const rows: FlatRow[] = [];
231
276
  if (selectAll) rows.push(SELECT_ALL_SENTINEL);
232
- for (const group of groups) {
233
- rows.push(makeGroupHeaderSentinel(group.value));
234
- rows.push(...group.items);
277
+ if (groups) {
278
+ for (const group of groups) {
279
+ rows.push(makeGroupHeaderSentinel(group.value));
280
+ rows.push(...group.items);
281
+ }
282
+ } else {
283
+ rows.push(...visibleData);
235
284
  }
236
285
  return rows;
237
- }, [groups, selectHeadings, selectAll]);
286
+ }, [groups, visibleData, selectHeadings, selectAll]);
238
287
 
239
288
  function setSelectedItems(items: T[]) {
240
289
  if (!isControlled) setInternalValue(items);
@@ -243,6 +292,22 @@ export function MultiCombobox<T extends DataWithId>(
243
292
  }
244
293
 
245
294
  function handleValueChange(next: FlatRow[]) {
295
+ const creatableClicked = next.find(isCreatableSentinel);
296
+ if (creatableClicked) {
297
+ const newItem = onCreateProp?.(creatableClicked.label);
298
+ const realItems = next.filter(
299
+ (v): v is T =>
300
+ !isGroupHeaderSentinel(v) &&
301
+ !isSelectAllSentinel(v) &&
302
+ !isCreatableSentinel(v)
303
+ );
304
+ setSelectedItems(newItem ? [...realItems, newItem] : realItems);
305
+ skipNextInputChangeRef.current = true;
306
+ setInternalInputValue("");
307
+ onInputValueChange?.("");
308
+ return;
309
+ }
310
+
246
311
  const selectAllClicked = next.filter(isSelectAllSentinel);
247
312
  const groupSentinels = next.filter(isGroupHeaderSentinel);
248
313
  const realItems = next.filter(
@@ -283,8 +348,18 @@ export function MultiCombobox<T extends DataWithId>(
283
348
  setSelectedItems(updated);
284
349
  }
285
350
 
286
- function handleSimpleValueChange(newItems: T[]) {
287
- setSelectedItems(newItems);
351
+ function handleSimpleValueChange(newItems: FlatRow[]) {
352
+ const creatableClicked = newItems.find(isCreatableSentinel);
353
+ if (creatableClicked) {
354
+ const newItem = onCreateProp?.(creatableClicked.label);
355
+ const realItems = newItems.filter((v): v is T => !isCreatableSentinel(v));
356
+ setSelectedItems(newItem ? [...realItems, newItem] : realItems);
357
+ skipNextInputChangeRef.current = true;
358
+ setInternalInputValue("");
359
+ onInputValueChange?.("");
360
+ return;
361
+ }
362
+ setSelectedItems(newItems as T[]);
288
363
  }
289
364
 
290
365
  function removeItem(item: T, e: React.MouseEvent) {
@@ -308,7 +383,7 @@ export function MultiCombobox<T extends DataWithId>(
308
383
  return (
309
384
  <ComboboxSelectAllItem key="__selectAll" value={row}>
310
385
  <ComboboxGroupHeaderCheckbox $state={state} id="__selectAll" />
311
- Select all
386
+ {selectAllLabel}
312
387
  </ComboboxSelectAllItem>
313
388
  );
314
389
  }
@@ -338,6 +413,10 @@ export function MultiCombobox<T extends DataWithId>(
338
413
  );
339
414
  }
340
415
 
416
+ if (isCreatableSentinel(row)) {
417
+ return renderCreatableItem(row);
418
+ }
419
+
341
420
  const item = row as T;
342
421
  return (
343
422
  <ComboboxItem
@@ -354,6 +433,33 @@ export function MultiCombobox<T extends DataWithId>(
354
433
  );
355
434
  }
356
435
 
436
+ function renderCreatableItem(
437
+ sentinel: CreatableSentinel,
438
+ style?: React.CSSProperties,
439
+ index?: number,
440
+ measureRef?: (el: Element | null) => void
441
+ ) {
442
+ return (
443
+ <ComboboxItem
444
+ key="__creatable"
445
+ value={sentinel}
446
+ itemId="__creatable"
447
+ label={`Create "${sentinel.label}"`}
448
+ inputType="none"
449
+ renderItem={() => (
450
+ <>
451
+ <Icon name="plus-outline" size="mini" />
452
+ {`Create "${sentinel.label}"`}
453
+ </>
454
+ )}
455
+ style={style}
456
+ index={index}
457
+ data-index={index}
458
+ ref={measureRef}
459
+ />
460
+ );
461
+ }
462
+
357
463
  // ─── Items passed to Combobox.Root ────────────────────────────────────────
358
464
  // When selectHeadings: flatItems (flat array of group header sentinels + items).
359
465
  // When selectAll only: prepend SELECT_ALL_SENTINEL to data so Base UI tracks
@@ -361,8 +467,12 @@ export function MultiCombobox<T extends DataWithId>(
361
467
  // The sentinel is never in flatItems — it is always rendered manually above
362
468
  // Combobox.List, so we must not prepend it when flatItems is used.
363
469
 
364
- const rootItems =
470
+ const baseItems =
365
471
  flatItems ?? groups ?? (isChildrenMode ? undefined : visibleData);
472
+ const rootItems =
473
+ creatableSentinel && Array.isArray(baseItems)
474
+ ? ([...baseItems, creatableSentinel] as FlatRow[])
475
+ : baseItems;
366
476
 
367
477
  const hasSentinels = selectAll || selectHeadings;
368
478
 
@@ -379,8 +489,36 @@ export function MultiCombobox<T extends DataWithId>(
379
489
  virtualized={isVirtualized}
380
490
  open={open}
381
491
  disabled={props.disabled}
382
- onOpenChange={setOpen}
383
- filter={filter}
492
+ onOpenChange={(nextOpen, eventDetails) => {
493
+ if (!nextOpen && eventDetails.reason === "item-press") return;
494
+ setOpen(nextOpen);
495
+ }}
496
+ filter={
497
+ creatableProp
498
+ ? (item: unknown, query: string) => {
499
+ if (isCreatableSentinel(item)) return true;
500
+ if (filter) return filter(item, query);
501
+ // Default Base UI filter: match against itemToStringLabel
502
+ if (isGroupHeaderSentinel(item) || isSelectAllSentinel(item))
503
+ return true;
504
+ const label = itemToString(item as T);
505
+ return label.toLowerCase().includes(query.toLowerCase());
506
+ }
507
+ : filter
508
+ }
509
+ inputValue={creatableProp ? currentInputValue : inputValue}
510
+ onInputValueChange={
511
+ creatableProp
512
+ ? (v) => {
513
+ if (skipNextInputChangeRef.current) {
514
+ skipNextInputChangeRef.current = false;
515
+ return;
516
+ }
517
+ setInternalInputValue(v);
518
+ onInputValueChange?.(v);
519
+ }
520
+ : onInputValueChange
521
+ }
384
522
  onItemHighlighted={
385
523
  isVirtualized
386
524
  ? (item, { reason, index }) => {
@@ -406,20 +544,31 @@ export function MultiCombobox<T extends DataWithId>(
406
544
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
545
  (hasSentinels ? handleValueChange : handleSimpleValueChange) as any
408
546
  }
409
- itemToStringLabel={(item: FlatRow | null) => {
410
- if (!item || isGroupHeaderSentinel(item) || isSelectAllSentinel(item))
547
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
548
+ itemToStringLabel={(item: any) => {
549
+ if (
550
+ !item ||
551
+ isGroupHeaderSentinel(item) ||
552
+ isSelectAllSentinel(item) ||
553
+ isCreatableSentinel(item)
554
+ )
411
555
  return "";
412
556
  return itemToString(item as T);
413
557
  }}
414
- isItemEqualToValue={(a: FlatRow, b: FlatRow) => {
558
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
559
+ isItemEqualToValue={(a: any, b: any) => {
560
+ if (isCreatableSentinel(a) && isCreatableSentinel(b))
561
+ return a.label === b.label;
415
562
  if (isGroupHeaderSentinel(a) && isGroupHeaderSentinel(b))
416
563
  return a.groupName === b.groupName;
417
564
  if (isSelectAllSentinel(a) && isSelectAllSentinel(b)) return true;
418
565
  if (
419
566
  !isGroupHeaderSentinel(a) &&
420
567
  !isSelectAllSentinel(a) &&
568
+ !isCreatableSentinel(a) &&
421
569
  !isGroupHeaderSentinel(b) &&
422
- !isSelectAllSentinel(b)
570
+ !isSelectAllSentinel(b) &&
571
+ !isCreatableSentinel(b)
423
572
  )
424
573
  return (a as T).id === (b as T).id;
425
574
  return false;
@@ -458,6 +607,7 @@ export function MultiCombobox<T extends DataWithId>(
458
607
  <ComboboxTokenInput
459
608
  id={id}
460
609
  placeholder={value.length > 0 ? "" : placeholder}
610
+ aria-describedby={ariaDescribedBy}
461
611
  />
462
612
  <ComboboxActionButtons>
463
613
  <ComboboxClear aria-label="Clear all">
@@ -508,57 +658,83 @@ export function MultiCombobox<T extends DataWithId>(
508
658
  inputType={inputType}
509
659
  renderItem={renderItem}
510
660
  renderGroupHeading={renderGroupHeading}
661
+ renderCreatableItem={
662
+ creatableProp
663
+ ? (sentinel, style, index, measureRef) =>
664
+ renderCreatableItem(
665
+ sentinel,
666
+ style,
667
+ index,
668
+ measureRef
669
+ )
670
+ : undefined
671
+ }
511
672
  />
512
673
  ) : children ? (
513
674
  children
514
675
  ) : flatItems ? (
515
676
  (row: FlatRow) => renderSentinelRow(row)
516
677
  ) : groups ? (
517
- (group: { value: string; items: T[] }, index: number) => (
518
- <React.Fragment key={group.value}>
519
- <ComboboxGroup items={group.items}>
520
- <ComboboxGroupLabel>
521
- {renderGroupHeading
522
- ? renderGroupHeading(group.value)
523
- : group.value}
524
- </ComboboxGroupLabel>
525
- <Combobox.Collection>
526
- {(item: T) => (
527
- <ComboboxItem
528
- key={item.id}
529
- value={item}
530
- itemId={String(item.id)}
531
- label={itemToString(item)}
532
- disabled={item.disabled}
533
- inputType={inputType}
534
- renderItem={
535
- renderItem
536
- ? () => renderItem(item)
537
- : () => itemToString(item)
538
- }
539
- />
540
- )}
541
- </Combobox.Collection>
542
- </ComboboxGroup>
543
- {index < groups.length - 1 && <ComboboxSeparator />}
544
- </React.Fragment>
545
- )
678
+ (
679
+ group: { value: string; items: T[] } | CreatableSentinel,
680
+ index: number
681
+ ) => {
682
+ if (isCreatableSentinel(group)) {
683
+ return renderCreatableItem(group);
684
+ }
685
+ const g = group as { value: string; items: T[] };
686
+ return (
687
+ <React.Fragment key={g.value}>
688
+ <ComboboxGroup items={g.items}>
689
+ <ComboboxGroupLabel>
690
+ {renderGroupHeading
691
+ ? renderGroupHeading(g.value)
692
+ : g.value}
693
+ </ComboboxGroupLabel>
694
+ <Combobox.Collection>
695
+ {(item: T) => (
696
+ <ComboboxItem
697
+ key={item.id}
698
+ value={item}
699
+ itemId={String(item.id)}
700
+ label={itemToString(item)}
701
+ disabled={item.disabled}
702
+ inputType={inputType}
703
+ renderItem={
704
+ renderItem
705
+ ? () => renderItem(item)
706
+ : () => itemToString(item)
707
+ }
708
+ />
709
+ )}
710
+ </Combobox.Collection>
711
+ </ComboboxGroup>
712
+ {index < groups.length - 1 && <ComboboxSeparator />}
713
+ </React.Fragment>
714
+ );
715
+ }
546
716
  ) : (
547
- (item: T) => (
548
- <ComboboxItem
549
- key={item.id}
550
- value={item}
551
- itemId={String(item.id)}
552
- label={itemToString(item)}
553
- disabled={item.disabled}
554
- inputType={inputType}
555
- renderItem={
556
- renderItem
557
- ? () => renderItem(item)
558
- : () => itemToString(item)
559
- }
560
- />
561
- )
717
+ (item: T | CreatableSentinel) => {
718
+ if (isCreatableSentinel(item)) {
719
+ return renderCreatableItem(item);
720
+ }
721
+ const dataItem = item as T;
722
+ return (
723
+ <ComboboxItem
724
+ key={dataItem.id}
725
+ value={dataItem}
726
+ itemId={String(dataItem.id)}
727
+ label={itemToString(dataItem)}
728
+ disabled={dataItem.disabled}
729
+ inputType={inputType}
730
+ renderItem={
731
+ renderItem
732
+ ? () => renderItem(dataItem)
733
+ : () => itemToString(dataItem)
734
+ }
735
+ />
736
+ );
737
+ }
562
738
  )}
563
739
  </ComboboxList>
564
740
  </ComboboxPopup>
@@ -73,6 +73,7 @@ interface MultiSearchableSelectBaseProps {
73
73
  loadingText?: string;
74
74
  disabled?: boolean;
75
75
  filter?: ((item: unknown, query: string) => boolean) | null;
76
+ "aria-describedby"?: string;
76
77
  }
77
78
 
78
79
  interface MultiSearchableSelectDataProps<T extends DataWithId>
@@ -135,6 +136,7 @@ export function MultiSearchableSelect<T extends DataWithId>(
135
136
  loadingText = "Loading...",
136
137
  filter,
137
138
  } = props;
139
+ const ariaDescribedBy = props["aria-describedby"];
138
140
 
139
141
  const isChildrenMode = "children" in props && props.children != null;
140
142
 
@@ -416,7 +418,10 @@ export function MultiSearchableSelect<T extends DataWithId>(
416
418
  }}
417
419
  items={rootItems}
418
420
  >
419
- <SearchableSelectTokenTrigger id={id}>
421
+ <SearchableSelectTokenTrigger
422
+ id={id}
423
+ aria-describedby={ariaDescribedBy}
424
+ >
420
425
  {renderSelection ? (
421
426
  renderSelection(value)
422
427
  ) : value.length > 0 ? (
@@ -43,6 +43,7 @@ interface MultiSelectBaseProps {
43
43
  id?: string;
44
44
  placeholder?: string;
45
45
  disabled?: boolean;
46
+ "aria-describedby"?: string;
46
47
  }
47
48
 
48
49
  interface MultiSelectDataProps<T extends DataWithId>
@@ -88,6 +89,7 @@ export type MultiSelectProps<T extends DataWithId> =
88
89
 
89
90
  export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
90
91
  const { id, placeholder = "Select..." } = props;
92
+ const ariaDescribedBy = props["aria-describedby"];
91
93
 
92
94
  const isChildrenMode = "children" in props && props.children != null;
93
95
 
@@ -179,7 +181,7 @@ export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
179
181
  disabled={props.disabled}
180
182
  id={id}
181
183
  >
182
- <SelectTrigger>
184
+ <SelectTrigger aria-describedby={ariaDescribedBy}>
183
185
  <SelectValue>
184
186
  {() => {
185
187
  if (renderSelection) return renderSelection(selectedItems);
@@ -31,6 +31,7 @@ interface SingleComboboxBaseProps {
31
31
  emptyText?: string;
32
32
  disabled?: boolean;
33
33
  filter?: ((item: unknown, query: string) => boolean) | null;
34
+ "aria-describedby"?: string;
34
35
  }
35
36
 
36
37
  interface SingleComboboxDataBaseProps<T extends DataWithId>
@@ -89,6 +90,7 @@ export function SingleCombobox<T extends DataWithId>(
89
90
  emptyText = "No results found.",
90
91
  filter,
91
92
  } = props;
93
+ const ariaDescribedBy = props["aria-describedby"];
92
94
 
93
95
  const isChildrenMode = "children" in props && props.children != null;
94
96
 
@@ -204,7 +206,11 @@ export function SingleCombobox<T extends DataWithId>(
204
206
  }
205
207
  >
206
208
  <ComboboxInputGroup>
207
- <ComboboxInput placeholder={placeholder} id={id} />
209
+ <ComboboxInput
210
+ placeholder={placeholder}
211
+ id={id}
212
+ aria-describedby={ariaDescribedBy}
213
+ />
208
214
  <ComboboxActionButtons>
209
215
  <ComboboxClear aria-label="Clear selection">
210
216
  <ClearIcon />
@@ -41,6 +41,7 @@ interface SingleSearchableSelectBaseProps {
41
41
  loadingText?: string;
42
42
  disabled?: boolean;
43
43
  filter?: ((item: unknown, query: string) => boolean) | null;
44
+ "aria-describedby"?: string;
44
45
  }
45
46
 
46
47
  interface SingleSearchableSelectDataBaseProps<T extends DataWithId>
@@ -106,6 +107,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
106
107
  loadingText = "Loading...",
107
108
  filter,
108
109
  } = props;
110
+ const ariaDescribedBy = props["aria-describedby"];
109
111
 
110
112
  const isChildrenMode = "children" in props && props.children != null;
111
113
 
@@ -236,7 +238,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
236
238
  : undefined
237
239
  }
238
240
  >
239
- <SearchableSelectTrigger id={id}>
241
+ <SearchableSelectTrigger id={id} aria-describedby={ariaDescribedBy}>
240
242
  <SearchableSelectPlaceholder>
241
243
  <Combobox.Value placeholder={placeholder}>
242
244
  {value
@@ -37,6 +37,25 @@ export const Basic: Story = {
37
37
  ),
38
38
  };
39
39
 
40
+ export const Empty: Story = {
41
+ name: "Empty",
42
+ render: () => (
43
+ <div style={{ width: "300px" }}>
44
+ <FormField label="Pick a book">
45
+ {({ id }) => (
46
+ <SingleSelect
47
+ id={"placeholder"}
48
+ disabled
49
+ data={[]}
50
+ itemToString={() => ""}
51
+ placeholder=""
52
+ />
53
+ )}
54
+ </FormField>
55
+ </div>
56
+ ),
57
+ };
58
+
40
59
  export const Disabled: Story = {
41
60
  name: "Disabled",
42
61
  render: () => (
@@ -39,6 +39,7 @@ interface SingleSelectBaseProps {
39
39
  placeholder?: string;
40
40
  includePlaceholderItem?: boolean;
41
41
  disabled?: boolean;
42
+ "aria-describedby"?: string;
42
43
  }
43
44
 
44
45
  interface SingleSelectDataProps<T extends DataWithId>
@@ -80,6 +81,7 @@ export function SingleSelect<T extends DataWithId>(
80
81
  props: SingleSelectProps<T>
81
82
  ) {
82
83
  const { id, placeholder, includePlaceholderItem } = props;
84
+ const ariaDescribedBy = props["aria-describedby"];
83
85
 
84
86
  const isChildrenMode = "children" in props && props.children != null;
85
87
 
@@ -166,7 +168,7 @@ export function SingleSelect<T extends DataWithId>(
166
168
  onValueChange={handleValueChange}
167
169
  id={id}
168
170
  >
169
- <SelectTrigger>
171
+ <SelectTrigger aria-describedby={ariaDescribedBy}>
170
172
  <StyledValue placeholder={placeholder}>
171
173
  {renderSelection && selectedItem
172
174
  ? renderSelection(selectedItem)