@sproutsocial/seeds-react-menu 1.17.0 → 1.18.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 +14 -22
- package/CHANGELOG.md +30 -0
- package/dist/esm/index.js +542 -229
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v3/index.js +793 -1038
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/v3/index.d.mts +64 -38
- package/dist/v3/index.d.ts +64 -38
- package/dist/v3/index.js +813 -1058
- package/dist/v3/index.js.map +1 -1
- package/dist/v3/menu.css +947 -0
- package/package.json +20 -21
- package/src/v3/ActionMenuStyles.tsx +169 -149
- package/src/v3/Common/ComboboxStyles.tsx +396 -511
- package/src/v3/Common/SelectIcons.tsx +17 -8
- package/src/v3/Common/SelectItem.tsx +25 -38
- package/src/v3/Common/SelectStyles.tsx +146 -144
- package/src/v3/Common/cn.ts +38 -0
- package/src/v3/MenuButtonStyles.tsx +55 -34
- package/src/v3/MultiSearchableSelect.tsx +8 -8
- package/src/v3/MultiSelect.tsx +11 -16
- package/src/v3/SingleSelect.tsx +13 -13
- package/src/v3/menu.css +947 -0
- package/tsup.config.ts +0 -1
- package/dist/esm/chunk-ROQATIB7.js +0 -357
- package/dist/esm/chunk-ROQATIB7.js.map +0 -1
- package/dist/esm/v2/index.js +0 -2089
- package/dist/esm/v2/index.js.map +0 -1
- package/dist/v2/index.d.mts +0 -108
- package/dist/v2/index.d.ts +0 -108
- package/dist/v2/index.js +0 -2280
- package/dist/v2/index.js.map +0 -1
- package/src/v2/Autocomplete/Autocomplete.stories.tsx +0 -645
- package/src/v2/Autocomplete/MultiAutocomplete.tsx +0 -487
- package/src/v2/Autocomplete/SingleAutocomplete.tsx +0 -366
- package/src/v2/Autocomplete/index.ts +0 -2
- package/src/v2/Common-V2/ComboboxContent.tsx +0 -457
- package/src/v2/Common-V2/MenuGroup.tsx +0 -60
- package/src/v2/Common-V2/MenuGroupTypes.ts +0 -30
- package/src/v2/Common-V2/MenuHeading.tsx +0 -83
- package/src/v2/Common-V2/MenuItem.tsx +0 -138
- package/src/v2/Common-V2/Popout.tsx +0 -124
- package/src/v2/Common-V2/PopoutTypes.tsx +0 -109
- package/src/v2/Common-V2/SelectToggleButton.tsx +0 -99
- package/src/v2/Common-V2/index.ts +0 -11
- package/src/v2/Common-V2/props.ts +0 -84
- package/src/v2/Common-V2/styles.tsx +0 -41
- package/src/v2/Common-V2/types.ts +0 -16
- package/src/v2/Common-V2/useHighlightedIndex.ts +0 -62
- package/src/v2/Common-V2/utils.ts +0 -238
- package/src/v2/SearchableSelect/AutocompleteContent.tsx +0 -325
- package/src/v2/SearchableSelect/SearchableMultiSelect.tsx +0 -527
- package/src/v2/SearchableSelect/SearchableSelect.tsx +0 -395
- package/src/v2/SearchableSelect/index.ts +0 -12
- package/src/v2/Select/MultiSelect.tsx +0 -417
- package/src/v2/Select/Select.stories.tsx +0 -593
- package/src/v2/Select/SingleSelect.tsx +0 -285
- package/src/v2/Select/index.ts +0 -2
- package/src/v2/index.ts +0 -2
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo } from "react";
|
|
2
|
-
import { useSelect } from "downshift";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import { Label } from "@sproutsocial/seeds-react-label";
|
|
5
|
-
// This will eventually be imported from seeds-react-popout
|
|
6
|
-
import { Popout } from "../Common-V2";
|
|
7
|
-
import {
|
|
8
|
-
isGroupHeading,
|
|
9
|
-
isToggleItem,
|
|
10
|
-
menuAnimationConfig,
|
|
11
|
-
groupItemsWithHeadings,
|
|
12
|
-
useHighlightedIndex,
|
|
13
|
-
createItemToStringForDownshift,
|
|
14
|
-
ComboboxContent,
|
|
15
|
-
SelectToggleButton,
|
|
16
|
-
} from "../Common-V2";
|
|
17
|
-
import {
|
|
18
|
-
type DataWithId,
|
|
19
|
-
type GroupedItem,
|
|
20
|
-
type ToggleItem,
|
|
21
|
-
type BaseSingleSelectProps,
|
|
22
|
-
} from "../Common-V2";
|
|
23
|
-
|
|
24
|
-
function defaultReducer(state, actionAndChanges) {
|
|
25
|
-
const { changes, type } = actionAndChanges;
|
|
26
|
-
|
|
27
|
-
switch (type) {
|
|
28
|
-
case useSelect.stateChangeTypes.ItemClick:
|
|
29
|
-
return {
|
|
30
|
-
...changes,
|
|
31
|
-
isOpen: false, // close the menu after selection for single select
|
|
32
|
-
highlightedIndex: 0, // reset highlight
|
|
33
|
-
};
|
|
34
|
-
default:
|
|
35
|
-
return changes;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface SingleSelectProps<T extends DataWithId>
|
|
40
|
-
extends BaseSingleSelectProps<T> {
|
|
41
|
-
itemToSelection?: (item: T) => ReactNode;
|
|
42
|
-
renderSelection?: (item: T | null) => ReactNode;
|
|
43
|
-
triggerButtonProps?: any;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function SingleSelect<T extends DataWithId>({
|
|
47
|
-
id,
|
|
48
|
-
data,
|
|
49
|
-
selectedItemId: controlledSelectedItemId,
|
|
50
|
-
isOpen: controlledIsOpen,
|
|
51
|
-
itemToKey = (item: T) => item?.id,
|
|
52
|
-
itemToString,
|
|
53
|
-
itemToSelection,
|
|
54
|
-
renderSelection,
|
|
55
|
-
renderItem,
|
|
56
|
-
groupBy,
|
|
57
|
-
renderGroupHeading,
|
|
58
|
-
isVirtualized = false,
|
|
59
|
-
estimatedItemHeight,
|
|
60
|
-
placeholder,
|
|
61
|
-
onSelectedItemIdChange,
|
|
62
|
-
onIsOpenChange,
|
|
63
|
-
stateReducer = defaultReducer,
|
|
64
|
-
triggerButtonProps,
|
|
65
|
-
menuProps,
|
|
66
|
-
inputType = "icon",
|
|
67
|
-
removePlaceholderItem = false,
|
|
68
|
-
onPointerDownOutside,
|
|
69
|
-
onInteractOutside,
|
|
70
|
-
popoutPlacement,
|
|
71
|
-
}: SingleSelectProps<T>) {
|
|
72
|
-
const [uncontrolledSelectedItemId, setUncontrolledSelectedItemId] = useState<
|
|
73
|
-
T["id"] | null
|
|
74
|
-
>(null);
|
|
75
|
-
const [uncontrolledIsOpen, setUncontrolledIsOpen] = useState<boolean>(false);
|
|
76
|
-
|
|
77
|
-
// Use controlled selectedItemId if provided, otherwise use internal state
|
|
78
|
-
const isSelectedItemIdControlled = controlledSelectedItemId !== undefined;
|
|
79
|
-
const selectedItemId = isSelectedItemIdControlled
|
|
80
|
-
? controlledSelectedItemId
|
|
81
|
-
: uncontrolledSelectedItemId;
|
|
82
|
-
|
|
83
|
-
// Convert selectedItemId to selectedItem for internal use
|
|
84
|
-
const selectedItem = useMemo<T | null>(() => {
|
|
85
|
-
if (selectedItemId === null || selectedItemId === undefined) {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
return data.find((item) => itemToKey(item) === selectedItemId) ?? null;
|
|
89
|
-
}, [selectedItemId, data, itemToKey]);
|
|
90
|
-
|
|
91
|
-
// Use controlled isOpen if provided, otherwise use internal state
|
|
92
|
-
const isIsOpenControlled = controlledIsOpen !== undefined;
|
|
93
|
-
const isOpen = isIsOpenControlled ? controlledIsOpen : uncontrolledIsOpen;
|
|
94
|
-
|
|
95
|
-
const updateSelectedItem = (newSelectedItem: T | null) => {
|
|
96
|
-
const newSelectedItemId = newSelectedItem
|
|
97
|
-
? itemToKey(newSelectedItem)
|
|
98
|
-
: null;
|
|
99
|
-
if (!isSelectedItemIdControlled) {
|
|
100
|
-
setUncontrolledSelectedItemId(newSelectedItemId);
|
|
101
|
-
}
|
|
102
|
-
onSelectedItemIdChange?.(newSelectedItemId);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const updateIsOpen = (newIsOpen: boolean) => {
|
|
106
|
-
if (!isIsOpenControlled) {
|
|
107
|
-
setUncontrolledIsOpen(newIsOpen);
|
|
108
|
-
}
|
|
109
|
-
onIsOpenChange?.(newIsOpen);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// TODO: This could be made generic as a hook and used in other components
|
|
113
|
-
// For Select, we show all items (no filtering)
|
|
114
|
-
const items = useMemo<GroupedItem<T>[]>(() => {
|
|
115
|
-
let groupedItems: GroupedItem<T>[];
|
|
116
|
-
|
|
117
|
-
const placeholderItem: ToggleItem = {
|
|
118
|
-
id: "clear-selection",
|
|
119
|
-
label: placeholder,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
if (groupBy) {
|
|
123
|
-
// Group first, then add placeholderItem at the beginning
|
|
124
|
-
groupedItems = groupItemsWithHeadings(data, groupBy, data, true);
|
|
125
|
-
} else {
|
|
126
|
-
groupedItems = data;
|
|
127
|
-
}
|
|
128
|
-
if (!removePlaceholderItem) {
|
|
129
|
-
groupedItems = [placeholderItem, ...groupedItems];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return groupedItems;
|
|
133
|
-
}, [data, groupBy, placeholder]);
|
|
134
|
-
|
|
135
|
-
// Filter out headings for downshift
|
|
136
|
-
const itemsForSelect = useMemo<(T | ToggleItem)[]>(() => {
|
|
137
|
-
// This is unnecessary for single select because we don't have group headings
|
|
138
|
-
return items.filter((item): item is T => !isGroupHeading(item));
|
|
139
|
-
}, [items]);
|
|
140
|
-
|
|
141
|
-
// Wrapper function to handle ToggleItem separately
|
|
142
|
-
const itemToStringForDownshift = useMemo(
|
|
143
|
-
() => createItemToStringForDownshift(itemToString),
|
|
144
|
-
[itemToString]
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
const selectProps = useSelect<T | ToggleItem>({
|
|
148
|
-
toggleButtonId: id,
|
|
149
|
-
items: itemsForSelect,
|
|
150
|
-
itemToString: itemToStringForDownshift,
|
|
151
|
-
// defaultHighlightedIndex: 0,
|
|
152
|
-
selectedItem: selectedItem,
|
|
153
|
-
isOpen,
|
|
154
|
-
stateReducer,
|
|
155
|
-
onStateChange({ type, selectedItem: newSelectedItem, isOpen: newIsOpen }) {
|
|
156
|
-
// Handle isOpen changes for any state change type
|
|
157
|
-
if (newIsOpen !== undefined) {
|
|
158
|
-
updateIsOpen(newIsOpen);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Handle item selection for both click and keyboard (Enter/Space) interactions
|
|
162
|
-
// When Enter/Space is pressed on a highlighted item, useSelect sets selectedItem
|
|
163
|
-
// We detect selection when selectedItem changes and it's a user interaction
|
|
164
|
-
const isSelectionEvent =
|
|
165
|
-
type === useSelect.stateChangeTypes.ItemClick ||
|
|
166
|
-
type === useSelect.stateChangeTypes.ToggleButtonKeyDownEnter;
|
|
167
|
-
|
|
168
|
-
if (isSelectionEvent && newSelectedItem && newSelectedItem !== null) {
|
|
169
|
-
// If placeholderItem is selected, set selectedItem to null
|
|
170
|
-
// Note: placeholderItem won't be in itemsForSelect, so this check is for safety
|
|
171
|
-
if (newSelectedItem.id === "clear-selection") {
|
|
172
|
-
updateSelectedItem(null);
|
|
173
|
-
} else {
|
|
174
|
-
updateSelectedItem(newSelectedItem as T);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const {
|
|
181
|
-
isOpen: selectIsOpen,
|
|
182
|
-
getToggleButtonProps,
|
|
183
|
-
getLabelProps,
|
|
184
|
-
getMenuProps,
|
|
185
|
-
highlightedIndex: selectHighlightedIndex,
|
|
186
|
-
getItemProps,
|
|
187
|
-
} = selectProps;
|
|
188
|
-
|
|
189
|
-
// Map the highlighted index from select (which only knows about items) to the full items array (which includes headings)
|
|
190
|
-
const highlightedIndex = useHighlightedIndex(
|
|
191
|
-
items,
|
|
192
|
-
itemsForSelect,
|
|
193
|
-
selectHighlightedIndex,
|
|
194
|
-
!!groupBy,
|
|
195
|
-
false // Single select components hardcode selectHeadings to false
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
const buttonContent = renderSelection
|
|
199
|
-
? renderSelection(selectedItem)
|
|
200
|
-
: selectedItem
|
|
201
|
-
? itemToSelection
|
|
202
|
-
? itemToSelection(selectedItem)
|
|
203
|
-
: itemToString(selectedItem)
|
|
204
|
-
: placeholder;
|
|
205
|
-
|
|
206
|
-
const toggleButtonProps = getToggleButtonProps();
|
|
207
|
-
// Downshift adds aria-labelledby by default, which can conflict with our Label usage
|
|
208
|
-
toggleButtonProps["aria-labelledby"] = undefined;
|
|
209
|
-
if (!selectIsOpen) {
|
|
210
|
-
// Remove aria-controls when the menu is closed to avoid referencing a non-existent element
|
|
211
|
-
// @ts-expect-error downshift types require aria-controls even though it is not required
|
|
212
|
-
toggleButtonProps["aria-controls"] = undefined;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<Popout
|
|
217
|
-
open={selectIsOpen}
|
|
218
|
-
onOpenChange={updateIsOpen}
|
|
219
|
-
side={popoutPlacement}
|
|
220
|
-
animationConfig={menuAnimationConfig}
|
|
221
|
-
onOpenAutoFocus={(e) => {
|
|
222
|
-
// Prevent default auto-focus behavior
|
|
223
|
-
e.preventDefault();
|
|
224
|
-
}}
|
|
225
|
-
onCloseAutoFocus={(e) => {
|
|
226
|
-
// Prevent default auto-focus behavior
|
|
227
|
-
e.preventDefault();
|
|
228
|
-
}}
|
|
229
|
-
onPointerDownOutside={onPointerDownOutside}
|
|
230
|
-
onInteractOutside={onInteractOutside}
|
|
231
|
-
content={
|
|
232
|
-
<ComboboxContent
|
|
233
|
-
items={items}
|
|
234
|
-
getMenuProps={getMenuProps}
|
|
235
|
-
getItemProps={(props: { index: number }) => {
|
|
236
|
-
// Only apply getItemProps to actual items, not headings or ToggleItems
|
|
237
|
-
const item = items[props.index];
|
|
238
|
-
if (isToggleItem(item)) {
|
|
239
|
-
// Handle ToggleItem (placeholderItem) clicks manually
|
|
240
|
-
return {
|
|
241
|
-
onClick: () => {
|
|
242
|
-
if (item.id === "clear-selection") {
|
|
243
|
-
updateSelectedItem(null);
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
const dataItem = item as T;
|
|
249
|
-
// Find the index in itemsForSelect using itemToKey for safer comparison
|
|
250
|
-
const itemIndex = itemsForSelect.findIndex((selectItem) => {
|
|
251
|
-
if (isToggleItem(selectItem)) {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
return itemToKey(selectItem as T) === itemToKey(dataItem);
|
|
255
|
-
});
|
|
256
|
-
if (itemIndex === -1) {
|
|
257
|
-
return {};
|
|
258
|
-
}
|
|
259
|
-
return getItemProps({ item: dataItem, index: itemIndex });
|
|
260
|
-
}}
|
|
261
|
-
selectedItems={selectedItem ? [selectedItem] : []}
|
|
262
|
-
highlightedIndex={highlightedIndex ?? -1}
|
|
263
|
-
renderItem={renderItem}
|
|
264
|
-
renderGroupHeading={renderGroupHeading}
|
|
265
|
-
itemToKey={itemToKey}
|
|
266
|
-
itemToString={itemToString}
|
|
267
|
-
inputType={inputType}
|
|
268
|
-
isVirtualized={isVirtualized}
|
|
269
|
-
estimatedItemHeight={estimatedItemHeight}
|
|
270
|
-
groupBy={groupBy}
|
|
271
|
-
selectHeadings={false}
|
|
272
|
-
{...menuProps}
|
|
273
|
-
/>
|
|
274
|
-
}
|
|
275
|
-
>
|
|
276
|
-
<SelectToggleButton
|
|
277
|
-
{...toggleButtonProps}
|
|
278
|
-
{...triggerButtonProps}
|
|
279
|
-
isOpen={selectIsOpen}
|
|
280
|
-
>
|
|
281
|
-
{buttonContent}
|
|
282
|
-
</SelectToggleButton>
|
|
283
|
-
</Popout>
|
|
284
|
-
);
|
|
285
|
-
}
|
package/src/v2/Select/index.ts
DELETED
package/src/v2/index.ts
DELETED