@sproutsocial/seeds-react-menu 1.12.2 → 1.13.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.
- package/.turbo/turbo-build.log +13 -13
- package/CHANGELOG.md +16 -0
- package/dist/esm/v3/index.js +517 -58
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/index.d.mts +22 -22
- package/dist/index.d.ts +22 -22
- package/dist/v3/index.d.mts +160 -50
- package/dist/v3/index.d.ts +160 -50
- package/dist/v3/index.js +522 -57
- package/dist/v3/index.js.map +1 -1
- package/package.json +1 -1
- package/src/v3/ActionMenu.stories.tsx +260 -0
- package/src/v3/ActionMenu.tsx +345 -0
- package/src/v3/ActionMenuStyles.tsx +153 -0
- package/src/v3/Common/SelectStyles.tsx +4 -3
- package/src/v3/ModalStories.stories.tsx +159 -0
- package/src/v3/MultiCombobox.stories.tsx +17 -0
- package/src/v3/MultiCombobox.tsx +24 -13
- package/src/v3/MultiSearchableSelect.stories.tsx +18 -0
- package/src/v3/MultiSearchableSelect.tsx +214 -55
- package/src/v3/MultiSelect.stories.tsx +17 -0
- package/src/v3/MultiSelect.tsx +3 -2
- package/src/v3/SingleCombobox.stories.tsx +17 -0
- package/src/v3/SingleCombobox.tsx +3 -1
- package/src/v3/SingleSearchableSelect.stories.tsx +18 -0
- package/src/v3/SingleSearchableSelect.tsx +3 -1
- package/src/v3/SingleSelect.stories.tsx +17 -0
- package/src/v3/SingleSelect.tsx +3 -2
- package/src/v3/index.ts +1 -0
|
@@ -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
|
ComboboxGroup,
|
|
@@ -26,6 +31,7 @@ import {
|
|
|
26
31
|
SearchableSelectList,
|
|
27
32
|
} from "./Common/ComboboxStyles";
|
|
28
33
|
import { ClearIcon, StyledChevron, ChevronIcon } from "./Common/SelectIcons";
|
|
34
|
+
import Icon from "@sproutsocial/seeds-react-icon";
|
|
29
35
|
import styled from "styled-components";
|
|
30
36
|
|
|
31
37
|
// ─── Styled components ────────────────────────────────────────────────────────
|
|
@@ -74,6 +80,7 @@ interface MultiSearchableSelectBaseProps {
|
|
|
74
80
|
disabled?: boolean;
|
|
75
81
|
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
76
82
|
"aria-describedby"?: string;
|
|
83
|
+
fullWidth?: boolean;
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
interface MultiSearchableSelectDataProps<T extends DataWithId>
|
|
@@ -94,6 +101,8 @@ interface MultiSearchableSelectDataProps<T extends DataWithId>
|
|
|
94
101
|
selectAll?: boolean;
|
|
95
102
|
isVirtualized?: boolean;
|
|
96
103
|
removeSelectedItems?: boolean;
|
|
104
|
+
creatable?: boolean;
|
|
105
|
+
onCreate?: (value: string) => T;
|
|
97
106
|
children?: never;
|
|
98
107
|
}
|
|
99
108
|
|
|
@@ -116,6 +125,8 @@ interface MultiSearchableSelectChildrenProps
|
|
|
116
125
|
selectAll?: never;
|
|
117
126
|
isVirtualized?: never;
|
|
118
127
|
removeSelectedItems?: never;
|
|
128
|
+
creatable?: never;
|
|
129
|
+
onCreate?: never;
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
export type MultiSearchableSelectProps<T extends DataWithId> =
|
|
@@ -135,6 +146,7 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
135
146
|
isLoading = false,
|
|
136
147
|
loadingText = "Loading...",
|
|
137
148
|
filter,
|
|
149
|
+
fullWidth = true,
|
|
138
150
|
} = props;
|
|
139
151
|
const ariaDescribedBy = props["aria-describedby"];
|
|
140
152
|
|
|
@@ -181,6 +193,12 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
181
193
|
const isVirtualized = isChildrenMode
|
|
182
194
|
? false
|
|
183
195
|
: (props as MultiSearchableSelectDataProps<T>).isVirtualized ?? false;
|
|
196
|
+
const creatableProp = isChildrenMode
|
|
197
|
+
? false
|
|
198
|
+
: (props as MultiSearchableSelectDataProps<T>).creatable ?? false;
|
|
199
|
+
const onCreateProp = isChildrenMode
|
|
200
|
+
? undefined
|
|
201
|
+
: (props as MultiSearchableSelectDataProps<T>).onCreate;
|
|
184
202
|
const children = isChildrenMode
|
|
185
203
|
? (props as MultiSearchableSelectChildrenProps).children
|
|
186
204
|
: undefined;
|
|
@@ -191,6 +209,8 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
191
209
|
|
|
192
210
|
const { containerRef, portalContainer } = useSeedsPortalContainer();
|
|
193
211
|
const [open, setOpen] = React.useState(false);
|
|
212
|
+
const [internalInputValue, setInternalInputValue] = React.useState("");
|
|
213
|
+
const skipNextInputChangeRef = React.useRef(false);
|
|
194
214
|
const virtualizerRef = React.useRef<ReturnType<
|
|
195
215
|
typeof useVirtualizer<HTMLDivElement, Element>
|
|
196
216
|
> | null>(null);
|
|
@@ -228,9 +248,27 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
228
248
|
[visibleData, groupBy]
|
|
229
249
|
);
|
|
230
250
|
|
|
251
|
+
const currentInputValue = internalInputValue;
|
|
252
|
+
|
|
253
|
+
const creatableSentinel = React.useMemo<CreatableSentinel | null>(() => {
|
|
254
|
+
if (!creatableProp) return null;
|
|
255
|
+
const trimmed = currentInputValue.trim();
|
|
256
|
+
if (!trimmed) return null;
|
|
257
|
+
const lowered = trimmed.toLocaleLowerCase();
|
|
258
|
+
const exactExists = data.some(
|
|
259
|
+
(d) => itemToString(d).trim().toLocaleLowerCase() === lowered
|
|
260
|
+
);
|
|
261
|
+
if (exactExists) return null;
|
|
262
|
+
return makeCreatableSentinel(trimmed);
|
|
263
|
+
}, [creatableProp, currentInputValue, data, itemToString]);
|
|
264
|
+
|
|
231
265
|
// ─── Sentinel flat list ───────────────────────────────────────────────────
|
|
232
266
|
|
|
233
|
-
type FlatRow =
|
|
267
|
+
type FlatRow =
|
|
268
|
+
| T
|
|
269
|
+
| GroupHeaderSentinel
|
|
270
|
+
| SelectAllSentinel
|
|
271
|
+
| CreatableSentinel;
|
|
234
272
|
|
|
235
273
|
const flatItems = React.useMemo<FlatRow[] | null>(() => {
|
|
236
274
|
if (!groups || (!selectHeadings && !selectAll)) return null;
|
|
@@ -250,10 +288,28 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
250
288
|
}
|
|
251
289
|
|
|
252
290
|
function handleValueChange(next: FlatRow[]) {
|
|
291
|
+
const creatableClicked = next.find(isCreatableSentinel);
|
|
292
|
+
if (creatableClicked) {
|
|
293
|
+
const newItem = onCreateProp?.(creatableClicked.label);
|
|
294
|
+
const realItems = next.filter(
|
|
295
|
+
(v): v is T =>
|
|
296
|
+
!isGroupHeaderSentinel(v) &&
|
|
297
|
+
!isSelectAllSentinel(v) &&
|
|
298
|
+
!isCreatableSentinel(v)
|
|
299
|
+
);
|
|
300
|
+
setSelectedItems(newItem ? [...realItems, newItem] : realItems);
|
|
301
|
+
skipNextInputChangeRef.current = true;
|
|
302
|
+
setInternalInputValue("");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
253
306
|
const selectAllClicked = next.filter(isSelectAllSentinel);
|
|
254
307
|
const groupSentinels = next.filter(isGroupHeaderSentinel);
|
|
255
308
|
const realItems = next.filter(
|
|
256
|
-
(v): v is T =>
|
|
309
|
+
(v): v is T =>
|
|
310
|
+
!isGroupHeaderSentinel(v) &&
|
|
311
|
+
!isSelectAllSentinel(v) &&
|
|
312
|
+
!isCreatableSentinel(v)
|
|
257
313
|
);
|
|
258
314
|
|
|
259
315
|
if (selectAllClicked.length > 0) {
|
|
@@ -290,8 +346,17 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
290
346
|
setSelectedItems(updated);
|
|
291
347
|
}
|
|
292
348
|
|
|
293
|
-
function handleSimpleValueChange(newItems:
|
|
294
|
-
|
|
349
|
+
function handleSimpleValueChange(newItems: FlatRow[]) {
|
|
350
|
+
const creatableClicked = newItems.find(isCreatableSentinel);
|
|
351
|
+
if (creatableClicked) {
|
|
352
|
+
const newItem = onCreateProp?.(creatableClicked.label);
|
|
353
|
+
const realItems = newItems.filter((v): v is T => !isCreatableSentinel(v));
|
|
354
|
+
setSelectedItems(newItem ? [...realItems, newItem] : realItems);
|
|
355
|
+
skipNextInputChangeRef.current = true;
|
|
356
|
+
setInternalInputValue("");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
setSelectedItems(newItems as T[]);
|
|
295
360
|
}
|
|
296
361
|
|
|
297
362
|
// ─── Render sentinel row ──────────────────────────────────────────────────
|
|
@@ -339,6 +404,10 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
339
404
|
);
|
|
340
405
|
}
|
|
341
406
|
|
|
407
|
+
if (isCreatableSentinel(row)) {
|
|
408
|
+
return renderCreatableItem(row);
|
|
409
|
+
}
|
|
410
|
+
|
|
342
411
|
const item = row as T;
|
|
343
412
|
return (
|
|
344
413
|
<ComboboxItem
|
|
@@ -355,21 +424,74 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
355
424
|
);
|
|
356
425
|
}
|
|
357
426
|
|
|
358
|
-
|
|
427
|
+
function renderCreatableItem(
|
|
428
|
+
sentinel: CreatableSentinel,
|
|
429
|
+
style?: React.CSSProperties,
|
|
430
|
+
index?: number,
|
|
431
|
+
measureRef?: (el: Element | null) => void
|
|
432
|
+
) {
|
|
433
|
+
return (
|
|
434
|
+
<ComboboxItem
|
|
435
|
+
key="__creatable"
|
|
436
|
+
value={sentinel}
|
|
437
|
+
itemId="__creatable"
|
|
438
|
+
label={`Create "${sentinel.label}"`}
|
|
439
|
+
inputType="none"
|
|
440
|
+
renderItem={() => (
|
|
441
|
+
<>
|
|
442
|
+
<Icon name="plus-outline" size="mini" />
|
|
443
|
+
{`Create "${sentinel.label}"`}
|
|
444
|
+
</>
|
|
445
|
+
)}
|
|
446
|
+
style={style}
|
|
447
|
+
index={index}
|
|
448
|
+
data-index={index}
|
|
449
|
+
ref={measureRef}
|
|
450
|
+
/>
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const baseItems =
|
|
359
455
|
flatItems ?? groups ?? (isChildrenMode ? undefined : visibleData);
|
|
456
|
+
const rootItems =
|
|
457
|
+
creatableSentinel && Array.isArray(baseItems)
|
|
458
|
+
? ([...baseItems, creatableSentinel] as FlatRow[])
|
|
459
|
+
: baseItems;
|
|
360
460
|
const hasSentinels = selectAll || selectHeadings;
|
|
361
461
|
const rootValue = hasSentinels
|
|
362
462
|
? ([...value] as FlatRow[])
|
|
363
463
|
: (value as unknown as T[]);
|
|
364
464
|
|
|
365
465
|
return (
|
|
366
|
-
<Field>
|
|
466
|
+
<Field $fullWidth={fullWidth}>
|
|
367
467
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
368
468
|
<Combobox.Root
|
|
369
469
|
multiple
|
|
370
470
|
id={id}
|
|
371
471
|
virtualized={isVirtualized}
|
|
372
|
-
filter={
|
|
472
|
+
filter={
|
|
473
|
+
creatableProp
|
|
474
|
+
? (item: unknown, query: string) => {
|
|
475
|
+
if (isCreatableSentinel(item)) return true;
|
|
476
|
+
if (filter) return filter(item, query);
|
|
477
|
+
if (isGroupHeaderSentinel(item) || isSelectAllSentinel(item))
|
|
478
|
+
return true;
|
|
479
|
+
const label = itemToString(item as T);
|
|
480
|
+
return label.toLowerCase().includes(query.toLowerCase());
|
|
481
|
+
}
|
|
482
|
+
: filter
|
|
483
|
+
}
|
|
484
|
+
inputValue={internalInputValue}
|
|
485
|
+
onInputValueChange={(v, eventDetails) => {
|
|
486
|
+
// Preserve search query when an item is selected. Base UI fires
|
|
487
|
+
// 'input-clear' on item-press; ignore it while the popup stays open.
|
|
488
|
+
if (eventDetails.reason === "input-clear" && open) return;
|
|
489
|
+
if (skipNextInputChangeRef.current) {
|
|
490
|
+
skipNextInputChangeRef.current = false;
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
setInternalInputValue(v);
|
|
494
|
+
}}
|
|
373
495
|
open={open}
|
|
374
496
|
disabled={props.disabled}
|
|
375
497
|
onOpenChange={setOpen}
|
|
@@ -398,20 +520,31 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
398
520
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
399
521
|
(hasSentinels ? handleValueChange : handleSimpleValueChange) as any
|
|
400
522
|
}
|
|
401
|
-
|
|
402
|
-
|
|
523
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
524
|
+
itemToStringLabel={(item: any) => {
|
|
525
|
+
if (
|
|
526
|
+
!item ||
|
|
527
|
+
isGroupHeaderSentinel(item) ||
|
|
528
|
+
isSelectAllSentinel(item) ||
|
|
529
|
+
isCreatableSentinel(item)
|
|
530
|
+
)
|
|
403
531
|
return "";
|
|
404
532
|
return itemToString(item as T);
|
|
405
533
|
}}
|
|
406
|
-
|
|
534
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
535
|
+
isItemEqualToValue={(a: any, b: any) => {
|
|
536
|
+
if (isCreatableSentinel(a) && isCreatableSentinel(b))
|
|
537
|
+
return a.label === b.label;
|
|
407
538
|
if (isGroupHeaderSentinel(a) && isGroupHeaderSentinel(b))
|
|
408
539
|
return a.groupName === b.groupName;
|
|
409
540
|
if (isSelectAllSentinel(a) && isSelectAllSentinel(b)) return true;
|
|
410
541
|
if (
|
|
411
542
|
!isGroupHeaderSentinel(a) &&
|
|
412
543
|
!isSelectAllSentinel(a) &&
|
|
544
|
+
!isCreatableSentinel(a) &&
|
|
413
545
|
!isGroupHeaderSentinel(b) &&
|
|
414
|
-
!isSelectAllSentinel(b)
|
|
546
|
+
!isSelectAllSentinel(b) &&
|
|
547
|
+
!isCreatableSentinel(b)
|
|
415
548
|
)
|
|
416
549
|
return (a as T).id === (b as T).id;
|
|
417
550
|
return false;
|
|
@@ -496,57 +629,83 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
496
629
|
inputType={inputType}
|
|
497
630
|
renderItem={renderItem}
|
|
498
631
|
renderGroupHeading={renderGroupHeading}
|
|
632
|
+
renderCreatableItem={
|
|
633
|
+
creatableProp
|
|
634
|
+
? (sentinel, style, index, measureRef) =>
|
|
635
|
+
renderCreatableItem(
|
|
636
|
+
sentinel,
|
|
637
|
+
style,
|
|
638
|
+
index,
|
|
639
|
+
measureRef
|
|
640
|
+
)
|
|
641
|
+
: undefined
|
|
642
|
+
}
|
|
499
643
|
/>
|
|
500
644
|
) : children ? (
|
|
501
645
|
children
|
|
502
646
|
) : flatItems ? (
|
|
503
647
|
(row: FlatRow) => renderSentinelRow(row)
|
|
504
648
|
) : groups ? (
|
|
505
|
-
(
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
649
|
+
(
|
|
650
|
+
group: { value: string; items: T[] } | CreatableSentinel,
|
|
651
|
+
index: number
|
|
652
|
+
) => {
|
|
653
|
+
if (isCreatableSentinel(group)) {
|
|
654
|
+
return renderCreatableItem(group);
|
|
655
|
+
}
|
|
656
|
+
const g = group as { value: string; items: T[] };
|
|
657
|
+
return (
|
|
658
|
+
<React.Fragment key={g.value}>
|
|
659
|
+
<ComboboxGroup items={g.items}>
|
|
660
|
+
<ComboboxGroupLabel>
|
|
661
|
+
{renderGroupHeading
|
|
662
|
+
? renderGroupHeading(g.value)
|
|
663
|
+
: g.value}
|
|
664
|
+
</ComboboxGroupLabel>
|
|
665
|
+
<Combobox.Collection>
|
|
666
|
+
{(item: T) => (
|
|
667
|
+
<ComboboxItem
|
|
668
|
+
key={item.id}
|
|
669
|
+
value={item}
|
|
670
|
+
itemId={String(item.id)}
|
|
671
|
+
label={itemToString(item)}
|
|
672
|
+
disabled={item.disabled}
|
|
673
|
+
inputType={inputType}
|
|
674
|
+
renderItem={
|
|
675
|
+
renderItem
|
|
676
|
+
? () => renderItem(item)
|
|
677
|
+
: () => itemToString(item)
|
|
678
|
+
}
|
|
679
|
+
/>
|
|
680
|
+
)}
|
|
681
|
+
</Combobox.Collection>
|
|
682
|
+
</ComboboxGroup>
|
|
683
|
+
{index < groups.length - 1 && <ComboboxSeparator />}
|
|
684
|
+
</React.Fragment>
|
|
685
|
+
);
|
|
686
|
+
}
|
|
534
687
|
) : (
|
|
535
|
-
(item: T) =>
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
688
|
+
(item: T | CreatableSentinel) => {
|
|
689
|
+
if (isCreatableSentinel(item)) {
|
|
690
|
+
return renderCreatableItem(item);
|
|
691
|
+
}
|
|
692
|
+
const dataItem = item as T;
|
|
693
|
+
return (
|
|
694
|
+
<ComboboxItem
|
|
695
|
+
key={dataItem.id}
|
|
696
|
+
value={dataItem}
|
|
697
|
+
itemId={String(dataItem.id)}
|
|
698
|
+
label={itemToString(dataItem)}
|
|
699
|
+
disabled={dataItem.disabled}
|
|
700
|
+
inputType={inputType}
|
|
701
|
+
renderItem={
|
|
702
|
+
renderItem
|
|
703
|
+
? () => renderItem(dataItem)
|
|
704
|
+
: () => itemToString(dataItem)
|
|
705
|
+
}
|
|
706
|
+
/>
|
|
707
|
+
);
|
|
708
|
+
}
|
|
550
709
|
)}
|
|
551
710
|
</SearchableSelectList>
|
|
552
711
|
</SearchableSelectPopup>
|
|
@@ -266,6 +266,23 @@ export const RemoveSelectedItems: Story = {
|
|
|
266
266
|
),
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
export const Inline: Story = {
|
|
270
|
+
name: "Inline (fullWidth=false)",
|
|
271
|
+
render: () => (
|
|
272
|
+
<FormField label="Pick books">
|
|
273
|
+
{({ id }) => (
|
|
274
|
+
<MultiSelect
|
|
275
|
+
id={id}
|
|
276
|
+
data={books}
|
|
277
|
+
itemToString={itemToString}
|
|
278
|
+
placeholder="Select books..."
|
|
279
|
+
fullWidth={false}
|
|
280
|
+
/>
|
|
281
|
+
)}
|
|
282
|
+
</FormField>
|
|
283
|
+
),
|
|
284
|
+
};
|
|
285
|
+
|
|
269
286
|
export const Children: Story = {
|
|
270
287
|
name: "Children API",
|
|
271
288
|
render: () => (
|
package/src/v3/MultiSelect.tsx
CHANGED
|
@@ -44,6 +44,7 @@ interface MultiSelectBaseProps {
|
|
|
44
44
|
placeholder?: string;
|
|
45
45
|
disabled?: boolean;
|
|
46
46
|
"aria-describedby"?: string;
|
|
47
|
+
fullWidth?: boolean;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
interface MultiSelectDataProps<T extends DataWithId>
|
|
@@ -88,7 +89,7 @@ export type MultiSelectProps<T extends DataWithId> =
|
|
|
88
89
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
89
90
|
|
|
90
91
|
export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
|
|
91
|
-
const { id, placeholder = "Select..." } = props;
|
|
92
|
+
const { id, placeholder = "Select...", fullWidth = true } = props;
|
|
92
93
|
const ariaDescribedBy = props["aria-describedby"];
|
|
93
94
|
|
|
94
95
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -171,7 +172,7 @@ export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
|
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
return (
|
|
174
|
-
<Field>
|
|
175
|
+
<Field $fullWidth={fullWidth}>
|
|
175
176
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
176
177
|
<Select.Root
|
|
177
178
|
multiple
|
|
@@ -192,6 +192,23 @@ export const Virtualized: Story = {
|
|
|
192
192
|
),
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
+
export const Inline: Story = {
|
|
196
|
+
name: "Inline (fullWidth=false)",
|
|
197
|
+
render: () => (
|
|
198
|
+
<FormField label="Pick a book">
|
|
199
|
+
{({ id }) => (
|
|
200
|
+
<SingleCombobox
|
|
201
|
+
id={id}
|
|
202
|
+
data={books}
|
|
203
|
+
itemToString={itemToString}
|
|
204
|
+
placeholder="Search books..."
|
|
205
|
+
fullWidth={false}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
</FormField>
|
|
209
|
+
),
|
|
210
|
+
};
|
|
211
|
+
|
|
195
212
|
export const CustomSearch: Story = {
|
|
196
213
|
name: "Custom search (title + author)",
|
|
197
214
|
render: () => (
|
|
@@ -32,6 +32,7 @@ interface SingleComboboxBaseProps {
|
|
|
32
32
|
disabled?: boolean;
|
|
33
33
|
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
34
34
|
"aria-describedby"?: string;
|
|
35
|
+
fullWidth?: boolean;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
interface SingleComboboxDataBaseProps<T extends DataWithId>
|
|
@@ -89,6 +90,7 @@ export function SingleCombobox<T extends DataWithId>(
|
|
|
89
90
|
placeholder = "Search...",
|
|
90
91
|
emptyText = "No results found.",
|
|
91
92
|
filter,
|
|
93
|
+
fullWidth = true,
|
|
92
94
|
} = props;
|
|
93
95
|
const ariaDescribedBy = props["aria-describedby"];
|
|
94
96
|
|
|
@@ -168,7 +170,7 @@ export function SingleCombobox<T extends DataWithId>(
|
|
|
168
170
|
);
|
|
169
171
|
|
|
170
172
|
return (
|
|
171
|
-
<Field>
|
|
173
|
+
<Field $fullWidth={fullWidth}>
|
|
172
174
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
173
175
|
<Combobox.Root
|
|
174
176
|
id={id}
|
|
@@ -304,6 +304,24 @@ export const LoadingWithToggle: Story = {
|
|
|
304
304
|
},
|
|
305
305
|
};
|
|
306
306
|
|
|
307
|
+
export const Inline: Story = {
|
|
308
|
+
name: "Inline (fullWidth=false)",
|
|
309
|
+
render: () => (
|
|
310
|
+
<FormField label="Pick a book">
|
|
311
|
+
{({ id }) => (
|
|
312
|
+
<SingleSearchableSelect
|
|
313
|
+
id={id}
|
|
314
|
+
data={books}
|
|
315
|
+
itemToString={itemToString}
|
|
316
|
+
placeholder="Select a book"
|
|
317
|
+
searchPlaceholder="Search books..."
|
|
318
|
+
fullWidth={false}
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
321
|
+
</FormField>
|
|
322
|
+
),
|
|
323
|
+
};
|
|
324
|
+
|
|
307
325
|
export const CustomSearch: Story = {
|
|
308
326
|
name: "Custom search (title + author)",
|
|
309
327
|
render: () => (
|
|
@@ -42,6 +42,7 @@ interface SingleSearchableSelectBaseProps {
|
|
|
42
42
|
disabled?: boolean;
|
|
43
43
|
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
44
44
|
"aria-describedby"?: string;
|
|
45
|
+
fullWidth?: boolean;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
interface SingleSearchableSelectDataBaseProps<T extends DataWithId>
|
|
@@ -106,6 +107,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
106
107
|
isLoading = false,
|
|
107
108
|
loadingText = "Loading...",
|
|
108
109
|
filter,
|
|
110
|
+
fullWidth = true,
|
|
109
111
|
} = props;
|
|
110
112
|
const ariaDescribedBy = props["aria-describedby"];
|
|
111
113
|
|
|
@@ -190,7 +192,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
190
192
|
);
|
|
191
193
|
|
|
192
194
|
return (
|
|
193
|
-
<Field>
|
|
195
|
+
<Field $fullWidth={fullWidth}>
|
|
194
196
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
195
197
|
<Combobox.Root
|
|
196
198
|
id={id}
|
|
@@ -259,6 +259,23 @@ export const DisabledItems: Story = {
|
|
|
259
259
|
),
|
|
260
260
|
};
|
|
261
261
|
|
|
262
|
+
export const Inline: Story = {
|
|
263
|
+
name: "Inline (fullWidth=false)",
|
|
264
|
+
render: () => (
|
|
265
|
+
<FormField label="Pick a book">
|
|
266
|
+
{({ id }) => (
|
|
267
|
+
<SingleSelect
|
|
268
|
+
id={id}
|
|
269
|
+
data={books}
|
|
270
|
+
itemToString={itemToString}
|
|
271
|
+
placeholder="Select a book"
|
|
272
|
+
fullWidth={false}
|
|
273
|
+
/>
|
|
274
|
+
)}
|
|
275
|
+
</FormField>
|
|
276
|
+
),
|
|
277
|
+
};
|
|
278
|
+
|
|
262
279
|
export const Children: Story = {
|
|
263
280
|
name: "Children API",
|
|
264
281
|
render: () => (
|
package/src/v3/SingleSelect.tsx
CHANGED
|
@@ -40,6 +40,7 @@ interface SingleSelectBaseProps {
|
|
|
40
40
|
includePlaceholderItem?: boolean;
|
|
41
41
|
disabled?: boolean;
|
|
42
42
|
"aria-describedby"?: string;
|
|
43
|
+
fullWidth?: boolean;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
interface SingleSelectDataProps<T extends DataWithId>
|
|
@@ -80,7 +81,7 @@ export type SingleSelectProps<T extends DataWithId> =
|
|
|
80
81
|
export function SingleSelect<T extends DataWithId>(
|
|
81
82
|
props: SingleSelectProps<T>
|
|
82
83
|
) {
|
|
83
|
-
const { id, placeholder, includePlaceholderItem } = props;
|
|
84
|
+
const { id, placeholder, includePlaceholderItem, fullWidth = true } = props;
|
|
84
85
|
const ariaDescribedBy = props["aria-describedby"];
|
|
85
86
|
|
|
86
87
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -159,7 +160,7 @@ export function SingleSelect<T extends DataWithId>(
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
return (
|
|
162
|
-
<Field>
|
|
163
|
+
<Field $fullWidth={fullWidth}>
|
|
163
164
|
<div ref={containerRef} style={{ position: "relative" }} />
|
|
164
165
|
<Select.Root
|
|
165
166
|
items={items}
|
package/src/v3/index.ts
CHANGED