@sproutsocial/seeds-react-menu 1.8.6 → 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 +48 -0
- package/dist/esm/v2/index.js +45 -8
- package/dist/esm/v2/index.js.map +1 -1
- package/dist/v2/index.d.mts +9 -4
- package/dist/v2/index.d.ts +9 -4
- package/dist/v2/index.js +44 -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 +21 -1
- package/src/v2/Autocomplete/SingleAutocomplete.tsx +2 -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 +5 -0
- package/src/v2/SearchableSelect/SearchableMultiSelect.tsx +2 -0
- package/src/v2/SearchableSelect/SearchableSelect.tsx +2 -0
- package/src/v2/Select/MultiSelect.tsx +2 -0
- package/src/v2/Select/SingleSelect.tsx +2 -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
|
|
@@ -74,7 +74,10 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
74
74
|
inputType = "checkbox",
|
|
75
75
|
onPointerDownOutside,
|
|
76
76
|
onInteractOutside,
|
|
77
|
+
popoutPlacement,
|
|
77
78
|
}: MultiAutocompleteProps<T>) {
|
|
79
|
+
const mousedownInPopoverRef = useRef(false);
|
|
80
|
+
|
|
78
81
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
79
82
|
useState<string>("");
|
|
80
83
|
const [uncontrolledSelectedItemIds, setUncontrolledSelectedItemIds] =
|
|
@@ -118,6 +121,9 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
118
121
|
};
|
|
119
122
|
|
|
120
123
|
const updateIsOpen = (newIsOpen: boolean) => {
|
|
124
|
+
if (!newIsOpen) {
|
|
125
|
+
updateInputValue("");
|
|
126
|
+
}
|
|
121
127
|
if (!isIsOpenControlled) {
|
|
122
128
|
setUncontrolledIsOpen(newIsOpen);
|
|
123
129
|
}
|
|
@@ -360,6 +366,13 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
360
366
|
if (inputProps.onBlur) {
|
|
361
367
|
inputProps.onBlur(e);
|
|
362
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
|
+
}
|
|
363
376
|
// Need to figure why onblur is getting called when selecting select all item
|
|
364
377
|
updateIsOpen(false);
|
|
365
378
|
};
|
|
@@ -373,6 +386,7 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
373
386
|
<Popout
|
|
374
387
|
open={comboboxIsOpen}
|
|
375
388
|
onOpenChange={updateIsOpen}
|
|
389
|
+
side={popoutPlacement}
|
|
376
390
|
onOpenAutoFocus={(e) => {
|
|
377
391
|
// Prevent default auto-focus behavior
|
|
378
392
|
e.preventDefault();
|
|
@@ -384,6 +398,12 @@ export function MultiAutocomplete<T extends DataWithId>({
|
|
|
384
398
|
onPointerDownOutside={onPointerDownOutside}
|
|
385
399
|
onInteractOutside={onInteractOutside}
|
|
386
400
|
animationConfig={menuAnimationConfig}
|
|
401
|
+
onMouseDown={() => {
|
|
402
|
+
mousedownInPopoverRef.current = true;
|
|
403
|
+
}}
|
|
404
|
+
onMouseUp={() => {
|
|
405
|
+
mousedownInPopoverRef.current = false;
|
|
406
|
+
}}
|
|
387
407
|
content={
|
|
388
408
|
<ComboboxContent
|
|
389
409
|
items={items}
|
|
@@ -67,6 +67,7 @@ export function SingleAutocomplete<T extends DataWithId>({
|
|
|
67
67
|
includePlaceholderItem = false,
|
|
68
68
|
onPointerDownOutside,
|
|
69
69
|
onInteractOutside,
|
|
70
|
+
popoutPlacement,
|
|
70
71
|
}: SingleAutocompleteProps<T>) {
|
|
71
72
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
72
73
|
useState<string>("");
|
|
@@ -293,6 +294,7 @@ export function SingleAutocomplete<T extends DataWithId>({
|
|
|
293
294
|
<Popout
|
|
294
295
|
open={comboboxIsOpen}
|
|
295
296
|
onOpenChange={updateIsOpen}
|
|
297
|
+
side={popoutPlacement}
|
|
296
298
|
animationConfig={menuAnimationConfig}
|
|
297
299
|
onOpenAutoFocus={(e) => {
|
|
298
300
|
// Prevent default auto-focus behavior
|
|
@@ -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}
|
|
@@ -22,6 +22,11 @@ export interface BaseSelectProps<T extends DataWithId> {
|
|
|
22
22
|
event: CustomEvent<{ originalEvent: PointerEvent }>
|
|
23
23
|
) => void;
|
|
24
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";
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* Props common to all single-select components
|
|
@@ -78,6 +78,7 @@ export function SearchableMultiSelect<T extends DataWithId>({
|
|
|
78
78
|
triggerButtonProps,
|
|
79
79
|
onPointerDownOutside,
|
|
80
80
|
onInteractOutside,
|
|
81
|
+
popoutPlacement,
|
|
81
82
|
}: SearchableMultiSelectProps<T>) {
|
|
82
83
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
83
84
|
useState<string>("");
|
|
@@ -367,6 +368,7 @@ export function SearchableMultiSelect<T extends DataWithId>({
|
|
|
367
368
|
<Popout
|
|
368
369
|
open={comboboxIsOpen}
|
|
369
370
|
onOpenChange={updateIsOpen}
|
|
371
|
+
side={popoutPlacement}
|
|
370
372
|
animationConfig={menuAnimationConfig}
|
|
371
373
|
onOpenAutoFocus={(e) => {
|
|
372
374
|
// Prevent default auto-focus behavior - we'll focus the input manually
|
|
@@ -74,6 +74,7 @@ export function SearchableSelect<T extends DataWithId>({
|
|
|
74
74
|
triggerButtonProps,
|
|
75
75
|
onPointerDownOutside,
|
|
76
76
|
onInteractOutside,
|
|
77
|
+
popoutPlacement,
|
|
77
78
|
}: SearchableSelectProps<T>) {
|
|
78
79
|
const [uncontrolledInputValue, setUncontrolledInputValue] =
|
|
79
80
|
useState<string>("");
|
|
@@ -257,6 +258,7 @@ export function SearchableSelect<T extends DataWithId>({
|
|
|
257
258
|
<Popout
|
|
258
259
|
open={comboboxIsOpen}
|
|
259
260
|
onOpenChange={updateIsOpen}
|
|
261
|
+
side={popoutPlacement}
|
|
260
262
|
animationConfig={menuAnimationConfig}
|
|
261
263
|
onOpenAutoFocus={(e) => {
|
|
262
264
|
// Prevent default auto-focus behavior - we'll focus the input manually
|
|
@@ -79,6 +79,7 @@ export function MultiSelect<T extends DataWithId>({
|
|
|
79
79
|
shouldOverflow,
|
|
80
80
|
onPointerDownOutside,
|
|
81
81
|
onInteractOutside,
|
|
82
|
+
popoutPlacement,
|
|
82
83
|
}: MultiSelectProps<T>) {
|
|
83
84
|
const [uncontrolledSelectedItemIds, setUncontrolledSelectedItemIds] =
|
|
84
85
|
useState<T["id"][]>([]);
|
|
@@ -335,6 +336,7 @@ export function MultiSelect<T extends DataWithId>({
|
|
|
335
336
|
<Popout
|
|
336
337
|
open={selectIsOpen}
|
|
337
338
|
onOpenChange={updateIsOpen}
|
|
339
|
+
side={popoutPlacement}
|
|
338
340
|
onOpenAutoFocus={(e) => {
|
|
339
341
|
// Prevent default auto-focus behavior
|
|
340
342
|
e.preventDefault();
|
|
@@ -66,6 +66,7 @@ export function SingleSelect<T extends DataWithId>({
|
|
|
66
66
|
removePlaceholderItem = false,
|
|
67
67
|
onPointerDownOutside,
|
|
68
68
|
onInteractOutside,
|
|
69
|
+
popoutPlacement,
|
|
69
70
|
}: SingleSelectProps<T>) {
|
|
70
71
|
const [uncontrolledSelectedItemId, setUncontrolledSelectedItemId] = useState<
|
|
71
72
|
T["id"] | null
|
|
@@ -214,6 +215,7 @@ export function SingleSelect<T extends DataWithId>({
|
|
|
214
215
|
<Popout
|
|
215
216
|
open={selectIsOpen}
|
|
216
217
|
onOpenChange={updateIsOpen}
|
|
218
|
+
side={popoutPlacement}
|
|
217
219
|
animationConfig={menuAnimationConfig}
|
|
218
220
|
onOpenAutoFocus={(e) => {
|
|
219
221
|
// Prevent default auto-focus behavior
|