@sproutsocial/seeds-react-menu 1.8.5 → 1.9.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.
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +64 -0
- package/dist/esm/v2/index.js +61 -8
- package/dist/esm/v2/index.js.map +1 -1
- package/dist/v2/index.d.mts +15 -4
- package/dist/v2/index.d.ts +15 -4
- package/dist/v2/index.js +60 -7
- package/dist/v2/index.js.map +1 -1
- package/package.json +13 -13
- package/src/v2/Autocomplete/Autocomplete.stories.tsx +36 -0
- package/src/v2/Autocomplete/MultiAutocomplete.tsx +25 -1
- package/src/v2/Autocomplete/SingleAutocomplete.tsx +6 -0
- package/src/v2/Common-V2/ComboboxContent.tsx +1 -0
- package/src/v2/Common-V2/Popout.tsx +22 -6
- package/src/v2/Common-V2/props.ts +9 -0
- package/src/v2/SearchableSelect/SearchableMultiSelect.tsx +6 -0
- package/src/v2/SearchableSelect/SearchableSelect.tsx +6 -0
- package/src/v2/Select/MultiSelect.tsx +6 -0
- package/src/v2/Select/SingleSelect.tsx +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproutsocial/seeds-react-menu",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "Seeds React Menu",
|
|
5
5
|
"author": "Sprout Social, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,18 +31,18 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@radix-ui/react-popover": "^1.1.2",
|
|
34
|
-
"@sproutsocial/seeds-react-box": "^1.1.
|
|
35
|
-
"@sproutsocial/seeds-react-button": "^1.
|
|
36
|
-
"@sproutsocial/seeds-react-checkbox": "^1.3.
|
|
37
|
-
"@sproutsocial/seeds-react-icon": "^2.2.
|
|
38
|
-
"@sproutsocial/seeds-react-input": "^1.5.
|
|
39
|
-
"@sproutsocial/seeds-react-label": "^1.0.
|
|
40
|
-
"@sproutsocial/seeds-react-popout": "^2.4.
|
|
41
|
-
"@sproutsocial/seeds-react-radio": "^1.3.
|
|
42
|
-
"@sproutsocial/seeds-react-switch": "^1.2.
|
|
34
|
+
"@sproutsocial/seeds-react-box": "^1.1.14",
|
|
35
|
+
"@sproutsocial/seeds-react-button": "^1.4.0",
|
|
36
|
+
"@sproutsocial/seeds-react-checkbox": "^1.3.24",
|
|
37
|
+
"@sproutsocial/seeds-react-icon": "^2.2.5",
|
|
38
|
+
"@sproutsocial/seeds-react-input": "^1.5.7",
|
|
39
|
+
"@sproutsocial/seeds-react-label": "^1.0.14",
|
|
40
|
+
"@sproutsocial/seeds-react-popout": "^2.4.28",
|
|
41
|
+
"@sproutsocial/seeds-react-radio": "^1.3.14",
|
|
42
|
+
"@sproutsocial/seeds-react-switch": "^1.2.23",
|
|
43
43
|
"@sproutsocial/seeds-react-system-props": "^3.0.2",
|
|
44
|
-
"@sproutsocial/seeds-react-theme": "^3.
|
|
45
|
-
"@sproutsocial/seeds-react-token-input": "^1.4.
|
|
44
|
+
"@sproutsocial/seeds-react-theme": "^3.6.0",
|
|
45
|
+
"@sproutsocial/seeds-react-token-input": "^1.4.28",
|
|
46
46
|
"@sproutsocial/seeds-react-utilities": "^4.3.0",
|
|
47
47
|
"@tanstack/react-virtual": "^3.13.12",
|
|
48
48
|
"downshift": "9.0.4",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"styled-system": "^5.1.5"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@sproutsocial/seeds-react-tooltip": "^1.1.
|
|
54
|
+
"@sproutsocial/seeds-react-tooltip": "^1.1.10",
|
|
55
55
|
"@sproutsocial/eslint-config-seeds": "*",
|
|
56
56
|
"@sproutsocial/seeds-react-testing-library": "*",
|
|
57
57
|
"@sproutsocial/seeds-testing": "*",
|
|
@@ -345,6 +345,8 @@ const TestVirtualizedMulti = () => {
|
|
|
345
345
|
itemToSearchString={itemToSearchString}
|
|
346
346
|
renderItem={renderItem}
|
|
347
347
|
isVirtualized={true}
|
|
348
|
+
groupBy={(book) => book.author}
|
|
349
|
+
selectHeadings={true}
|
|
348
350
|
EmptyState={defaultEmptyState}
|
|
349
351
|
placeholder="Search for books"
|
|
350
352
|
menuProps={{
|
|
@@ -607,3 +609,37 @@ export const CustomSearchString: Story = {
|
|
|
607
609
|
return <TestCustomSearchString />;
|
|
608
610
|
},
|
|
609
611
|
};
|
|
612
|
+
|
|
613
|
+
// Popout Placement Top
|
|
614
|
+
// Positioned at the bottom of the viewport to demonstrate that the popout
|
|
615
|
+
// opens upward rather than downward when popoutPlacement="top" is set.
|
|
616
|
+
const TestPopoutPlacementTop = () => {
|
|
617
|
+
return (
|
|
618
|
+
<FormField label="Pick some books" width="100%">
|
|
619
|
+
{({ id }) => (
|
|
620
|
+
<MultiAutocomplete
|
|
621
|
+
id={id}
|
|
622
|
+
data={books}
|
|
623
|
+
itemToString={itemToString}
|
|
624
|
+
renderItem={renderItem}
|
|
625
|
+
onSelectedItemIdsChange={(itemIds) => console.log(itemIds)}
|
|
626
|
+
EmptyState={defaultEmptyState}
|
|
627
|
+
placeholder="Search for books"
|
|
628
|
+
popoutPlacement="top"
|
|
629
|
+
/>
|
|
630
|
+
)}
|
|
631
|
+
</FormField>
|
|
632
|
+
);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
export const PopoutPlacementTop: Story = {
|
|
636
|
+
name: "Popout Placement Top",
|
|
637
|
+
decorators: [
|
|
638
|
+
(Story) => (
|
|
639
|
+
<Box display="flex" height="100vh" alignItems="flex-end">
|
|
640
|
+
<Story />
|
|
641
|
+
</Box>
|
|
642
|
+
),
|
|
643
|
+
],
|
|
644
|
+
render: () => <TestPopoutPlacementTop />,
|
|
645
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo } from "react";
|
|
1
|
+
import { useState, useMemo, useRef } from "react";
|
|
2
2
|
import { useCombobox, useMultipleSelection } from "downshift";
|
|
3
3
|
import { TokenInput } from "@sproutsocial/seeds-react-token-input";
|
|
4
4
|
// This will eventually be imported from seeds-react-popout
|
|
@@ -72,7 +72,12 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
72
72
|
stateReducer = defaultReducer,
|
|
73
73
|
menuProps,
|
|
74
74
|
inputType = "checkbox",
|
|
75
|
+
onPointerDownOutside,
|
|
76
|
+
onInteractOutside,
|
|
77
|
+
popoutPlacement,
|
|
75
78
|
}: MultiAutocompleteProps<T>) {
|
|
79
|
+
const mousedownInPopoverRef = useRef(false);
|
|
80
|
+
|
|
76
81
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
77
82
|
useState<string>("");
|
|
78
83
|
const [uncontrolledSelectedItemIds, setUncontrolledSelectedItemIds] =
|
|
@@ -116,6 +121,9 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
116
121
|
};
|
|
117
122
|
|
|
118
123
|
const updateIsOpen = (newIsOpen: boolean) => {
|
|
124
|
+
if (!newIsOpen) {
|
|
125
|
+
updateInputValue("");
|
|
126
|
+
}
|
|
119
127
|
if (!isIsOpenControlled) {
|
|
120
128
|
setUncontrolledIsOpen(newIsOpen);
|
|
121
129
|
}
|
|
@@ -358,6 +366,13 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
358
366
|
if (inputProps.onBlur) {
|
|
359
367
|
inputProps.onBlur(e);
|
|
360
368
|
}
|
|
369
|
+
// Don't close if the blur was caused by a mousedown inside the popover (e.g. clicking the scrollbar)
|
|
370
|
+
// Re-focus the input so future blur events can still close the menu
|
|
371
|
+
if (mousedownInPopoverRef.current) {
|
|
372
|
+
mousedownInPopoverRef.current = false;
|
|
373
|
+
e.target.focus();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
361
376
|
// Need to figure why onblur is getting called when selecting select all item
|
|
362
377
|
updateIsOpen(false);
|
|
363
378
|
};
|
|
@@ -371,6 +386,7 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
371
386
|
<Popout
|
|
372
387
|
open={comboboxIsOpen}
|
|
373
388
|
onOpenChange={updateIsOpen}
|
|
389
|
+
side={popoutPlacement}
|
|
374
390
|
onOpenAutoFocus={(e) => {
|
|
375
391
|
// Prevent default auto-focus behavior
|
|
376
392
|
e.preventDefault();
|
|
@@ -379,7 +395,15 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
379
395
|
// Prevent default auto-focus behavior
|
|
380
396
|
e.preventDefault();
|
|
381
397
|
}}
|
|
398
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
399
|
+
onInteractOutside={onInteractOutside}
|
|
382
400
|
animationConfig={menuAnimationConfig}
|
|
401
|
+
onMouseDown={() => {
|
|
402
|
+
mousedownInPopoverRef.current = true;
|
|
403
|
+
}}
|
|
404
|
+
onMouseUp={() => {
|
|
405
|
+
mousedownInPopoverRef.current = false;
|
|
406
|
+
}}
|
|
383
407
|
content={
|
|
384
408
|
<ComboboxContent
|
|
385
409
|
items={items}
|
|
@@ -65,6 +65,9 @@ export function SingleAutocomplete<T extends DataWithId>({
|
|
|
65
65
|
menuProps,
|
|
66
66
|
inputType = "icon",
|
|
67
67
|
includePlaceholderItem = false,
|
|
68
|
+
onPointerDownOutside,
|
|
69
|
+
onInteractOutside,
|
|
70
|
+
popoutPlacement,
|
|
68
71
|
}: SingleAutocompleteProps<T>) {
|
|
69
72
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
70
73
|
useState<string>("");
|
|
@@ -291,6 +294,7 @@ export function SingleAutocomplete<T extends DataWithId>({
|
|
|
291
294
|
<Popout
|
|
292
295
|
open={comboboxIsOpen}
|
|
293
296
|
onOpenChange={updateIsOpen}
|
|
297
|
+
side={popoutPlacement}
|
|
294
298
|
animationConfig={menuAnimationConfig}
|
|
295
299
|
onOpenAutoFocus={(e) => {
|
|
296
300
|
// Prevent default auto-focus behavior
|
|
@@ -300,6 +304,8 @@ export function SingleAutocomplete<T extends DataWithId>({
|
|
|
300
304
|
// Prevent default auto-focus behavior
|
|
301
305
|
e.preventDefault();
|
|
302
306
|
}}
|
|
307
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
308
|
+
onInteractOutside={onInteractOutside}
|
|
303
309
|
content={
|
|
304
310
|
<ComboboxContent
|
|
305
311
|
items={items}
|
|
@@ -77,12 +77,28 @@ export function Popout({
|
|
|
77
77
|
// At the moment, if the popout content is the combobox the onclick gets overridden by the popout
|
|
78
78
|
return (
|
|
79
79
|
<Popover.Root open={popoverOpen} onOpenChange={popoverOnOpenChange}>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
{/*
|
|
81
|
+
The Anchor provides the positioning reference for the popout content.
|
|
82
|
+
We can't rely on Popover.Trigger's ref for positioning because the children
|
|
83
|
+
(e.g. Input) are class components that don't support forwardRef — Radix's
|
|
84
|
+
asChild would connect the ref to the class instance rather than the DOM node.
|
|
85
|
+
The Anchor is absolutely positioned at the top or bottom edge of the wrapper
|
|
86
|
+
so the popout content appears correctly above or below the trigger.
|
|
87
|
+
*/}
|
|
88
|
+
<div style={{ position: "relative" }}>
|
|
89
|
+
<Popover.Trigger ref={radixRef} asChild>
|
|
90
|
+
{children}
|
|
91
|
+
</Popover.Trigger>
|
|
92
|
+
<Popover.Anchor
|
|
93
|
+
style={{
|
|
94
|
+
position: "absolute",
|
|
95
|
+
top: side === "top" ? 0 : "100%",
|
|
96
|
+
left: 0,
|
|
97
|
+
right: 0,
|
|
98
|
+
height: "1px",
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
86
102
|
<Popover.Portal>
|
|
87
103
|
<StyledContent
|
|
88
104
|
side={side}
|
|
@@ -18,6 +18,15 @@ export interface BaseSelectProps<T extends DataWithId> {
|
|
|
18
18
|
onIsOpenChange?: (isOpen: boolean) => void;
|
|
19
19
|
stateReducer?: any;
|
|
20
20
|
menuProps?: React.HTMLAttributes<HTMLDivElement>;
|
|
21
|
+
onPointerDownOutside?: (
|
|
22
|
+
event: CustomEvent<{ originalEvent: PointerEvent }>
|
|
23
|
+
) => void;
|
|
24
|
+
onInteractOutside?: (event: CustomEvent<{ originalEvent: Event }>) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Controls which side the popout appears relative to the trigger.
|
|
27
|
+
* @default "bottom"
|
|
28
|
+
*/
|
|
29
|
+
popoutPlacement?: "top" | "bottom";
|
|
21
30
|
}
|
|
22
31
|
/**
|
|
23
32
|
* Props common to all single-select components
|
|
@@ -76,6 +76,9 @@ export function SearchableMultiSelect<T extends DataWithId>({
|
|
|
76
76
|
onIsOpenChange,
|
|
77
77
|
stateReducer = defaultReducer,
|
|
78
78
|
triggerButtonProps,
|
|
79
|
+
onPointerDownOutside,
|
|
80
|
+
onInteractOutside,
|
|
81
|
+
popoutPlacement,
|
|
79
82
|
}: SearchableMultiSelectProps<T>) {
|
|
80
83
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
81
84
|
useState<string>("");
|
|
@@ -365,6 +368,7 @@ export function SearchableMultiSelect<T extends DataWithId>({
|
|
|
365
368
|
<Popout
|
|
366
369
|
open={comboboxIsOpen}
|
|
367
370
|
onOpenChange={updateIsOpen}
|
|
371
|
+
side={popoutPlacement}
|
|
368
372
|
animationConfig={menuAnimationConfig}
|
|
369
373
|
onOpenAutoFocus={(e) => {
|
|
370
374
|
// Prevent default auto-focus behavior - we'll focus the input manually
|
|
@@ -383,6 +387,8 @@ export function SearchableMultiSelect<T extends DataWithId>({
|
|
|
383
387
|
// Prevent default auto-focus behavior
|
|
384
388
|
e.preventDefault();
|
|
385
389
|
}}
|
|
390
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
391
|
+
onInteractOutside={onInteractOutside}
|
|
386
392
|
content={
|
|
387
393
|
<div>
|
|
388
394
|
{/* Input inside the Popout */}
|
|
@@ -72,6 +72,9 @@ export function SearchableSelect<T extends DataWithId>({
|
|
|
72
72
|
onIsOpenChange,
|
|
73
73
|
stateReducer = defaultReducer,
|
|
74
74
|
triggerButtonProps,
|
|
75
|
+
onPointerDownOutside,
|
|
76
|
+
onInteractOutside,
|
|
77
|
+
popoutPlacement,
|
|
75
78
|
}: SearchableSelectProps<T>) {
|
|
76
79
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
77
80
|
useState<string>("");
|
|
@@ -255,6 +258,7 @@ export function SearchableSelect<T extends DataWithId>({
|
|
|
255
258
|
<Popout
|
|
256
259
|
open={comboboxIsOpen}
|
|
257
260
|
onOpenChange={updateIsOpen}
|
|
261
|
+
side={popoutPlacement}
|
|
258
262
|
animationConfig={menuAnimationConfig}
|
|
259
263
|
onOpenAutoFocus={(e) => {
|
|
260
264
|
// Prevent default auto-focus behavior - we'll focus the input manually
|
|
@@ -273,6 +277,8 @@ export function SearchableSelect<T extends DataWithId>({
|
|
|
273
277
|
// Prevent default auto-focus behavior
|
|
274
278
|
e.preventDefault();
|
|
275
279
|
}}
|
|
280
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
281
|
+
onInteractOutside={onInteractOutside}
|
|
276
282
|
content={
|
|
277
283
|
<div>
|
|
278
284
|
{/* Input inside the Popout */}
|
|
@@ -77,6 +77,9 @@ export function MultiSelect<T extends DataWithId>({
|
|
|
77
77
|
menuProps,
|
|
78
78
|
inputType = "checkbox",
|
|
79
79
|
shouldOverflow,
|
|
80
|
+
onPointerDownOutside,
|
|
81
|
+
onInteractOutside,
|
|
82
|
+
popoutPlacement,
|
|
80
83
|
}: MultiSelectProps<T>) {
|
|
81
84
|
const [uncontrolledSelectedItemIds, setUncontrolledSelectedItemIds] =
|
|
82
85
|
useState<T["id"][]>([]);
|
|
@@ -333,6 +336,7 @@ export function MultiSelect<T extends DataWithId>({
|
|
|
333
336
|
<Popout
|
|
334
337
|
open={selectIsOpen}
|
|
335
338
|
onOpenChange={updateIsOpen}
|
|
339
|
+
side={popoutPlacement}
|
|
336
340
|
onOpenAutoFocus={(e) => {
|
|
337
341
|
// Prevent default auto-focus behavior
|
|
338
342
|
e.preventDefault();
|
|
@@ -341,6 +345,8 @@ export function MultiSelect<T extends DataWithId>({
|
|
|
341
345
|
// Prevent default auto-focus behavior
|
|
342
346
|
e.preventDefault();
|
|
343
347
|
}}
|
|
348
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
349
|
+
onInteractOutside={onInteractOutside}
|
|
344
350
|
animationConfig={menuAnimationConfig}
|
|
345
351
|
content={
|
|
346
352
|
<ComboboxContent
|
|
@@ -64,6 +64,9 @@ export function SingleSelect<T extends DataWithId>({
|
|
|
64
64
|
menuProps,
|
|
65
65
|
inputType = "icon",
|
|
66
66
|
removePlaceholderItem = false,
|
|
67
|
+
onPointerDownOutside,
|
|
68
|
+
onInteractOutside,
|
|
69
|
+
popoutPlacement,
|
|
67
70
|
}: SingleSelectProps<T>) {
|
|
68
71
|
const [uncontrolledSelectedItemId, setUncontrolledSelectedItemId] = useState<
|
|
69
72
|
T["id"] | null
|
|
@@ -212,6 +215,7 @@ export function SingleSelect<T extends DataWithId>({
|
|
|
212
215
|
<Popout
|
|
213
216
|
open={selectIsOpen}
|
|
214
217
|
onOpenChange={updateIsOpen}
|
|
218
|
+
side={popoutPlacement}
|
|
215
219
|
animationConfig={menuAnimationConfig}
|
|
216
220
|
onOpenAutoFocus={(e) => {
|
|
217
221
|
// Prevent default auto-focus behavior
|
|
@@ -221,6 +225,8 @@ export function SingleSelect<T extends DataWithId>({
|
|
|
221
225
|
// Prevent default auto-focus behavior
|
|
222
226
|
e.preventDefault();
|
|
223
227
|
}}
|
|
228
|
+
onPointerDownOutside={onPointerDownOutside}
|
|
229
|
+
onInteractOutside={onInteractOutside}
|
|
224
230
|
content={
|
|
225
231
|
<ComboboxContent
|
|
226
232
|
items={items}
|