@sproutsocial/seeds-react-menu 1.17.0 → 1.18.1
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 +37 -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,238 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { MOTION_DURATION_FAST } from "@sproutsocial/seeds-motion/unitless";
|
|
3
|
-
import { type EnumPlacements } from "@sproutsocial/seeds-react-popout";
|
|
4
|
-
import type {
|
|
5
|
-
DataWithId,
|
|
6
|
-
GroupedItem,
|
|
7
|
-
GroupHeading,
|
|
8
|
-
ToggleItem,
|
|
9
|
-
} from "./types";
|
|
10
|
-
|
|
11
|
-
export function isGroupHeading<T>(item: GroupedItem<T>): item is GroupHeading {
|
|
12
|
-
return (
|
|
13
|
-
typeof item === "object" &&
|
|
14
|
-
item !== null &&
|
|
15
|
-
"type" in item &&
|
|
16
|
-
item.type === "heading"
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function isToggleItem<T>(
|
|
21
|
-
item: GroupedItem<T> | T | ToggleItem | null
|
|
22
|
-
): item is ToggleItem {
|
|
23
|
-
return (
|
|
24
|
-
typeof item === "object" &&
|
|
25
|
-
item !== null &&
|
|
26
|
-
"label" in item &&
|
|
27
|
-
!("type" in item) &&
|
|
28
|
-
Object.keys(item).length === 2 && // ToggleItem only has id and label
|
|
29
|
-
"id" in item
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const menuPlacementOrigin: {
|
|
34
|
-
[key in EnumPlacements]?: { originX: number; originY: number };
|
|
35
|
-
} = {
|
|
36
|
-
top: { originX: 0.5, originY: 1 },
|
|
37
|
-
["top-start"]: { originX: 0, originY: 1 },
|
|
38
|
-
["top-end"]: { originX: 1, originY: 1 },
|
|
39
|
-
bottom: { originX: 0.5, originY: 0 },
|
|
40
|
-
["bottom-start"]: { originX: 0, originY: 0 },
|
|
41
|
-
["bottom-end"]: { originX: 1, originY: 0 },
|
|
42
|
-
left: { originX: 1, originY: 0.5 },
|
|
43
|
-
["left-start"]: { originX: 1, originY: 0 },
|
|
44
|
-
["left-end"]: { originX: 1, originY: 1 },
|
|
45
|
-
right: { originX: 0, originY: 0.5 },
|
|
46
|
-
["right-start"]: { originX: 0, originY: 0 },
|
|
47
|
-
["right-end"]: { originX: 0, originY: 1 },
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const menuAnimationConfig = {
|
|
51
|
-
// Turning off layout true. This seems to be what causes the crazy animations when searching.
|
|
52
|
-
// layout: true,
|
|
53
|
-
variants: {
|
|
54
|
-
initial: (
|
|
55
|
-
custom: { placement: EnumPlacements } = { placement: "bottom" }
|
|
56
|
-
) => ({
|
|
57
|
-
...menuPlacementOrigin[custom.placement],
|
|
58
|
-
scale: 0,
|
|
59
|
-
opacity: 0,
|
|
60
|
-
}),
|
|
61
|
-
animate: (
|
|
62
|
-
custom: { placement: EnumPlacements } = { placement: "bottom" }
|
|
63
|
-
) => ({
|
|
64
|
-
...menuPlacementOrigin[custom.placement],
|
|
65
|
-
opacity: 1,
|
|
66
|
-
scale: 1,
|
|
67
|
-
|
|
68
|
-
transition: {
|
|
69
|
-
type: "tween",
|
|
70
|
-
duration: MOTION_DURATION_FAST,
|
|
71
|
-
ease: "circOut",
|
|
72
|
-
},
|
|
73
|
-
}),
|
|
74
|
-
exit: {
|
|
75
|
-
opacity: 0,
|
|
76
|
-
scale: 1,
|
|
77
|
-
transition: {
|
|
78
|
-
type: "tween",
|
|
79
|
-
duration: MOTION_DURATION_FAST,
|
|
80
|
-
ease: "circIn",
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
initial: "initial",
|
|
85
|
-
whileInView: "animate",
|
|
86
|
-
viewport: { once: true },
|
|
87
|
-
exit: "exit",
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export function getFilteredItems<T extends DataWithId>(
|
|
91
|
-
data: T[],
|
|
92
|
-
inputValue: string,
|
|
93
|
-
itemToSearchString: (item: T) => string,
|
|
94
|
-
itemToString: (item: T) => string,
|
|
95
|
-
options?: {
|
|
96
|
-
selectedItems?: T[];
|
|
97
|
-
selectedItem?: T | null;
|
|
98
|
-
showSelectedItems?: boolean;
|
|
99
|
-
}
|
|
100
|
-
) {
|
|
101
|
-
const lowerCasedInputValue = inputValue.toLowerCase();
|
|
102
|
-
|
|
103
|
-
// If the item string exactly matches the input value, return for all values
|
|
104
|
-
// This means an item was selected, and we should allow users to change easily
|
|
105
|
-
if (
|
|
106
|
-
options?.selectedItem &&
|
|
107
|
-
itemToString(options?.selectedItem).toLowerCase() === lowerCasedInputValue
|
|
108
|
-
) {
|
|
109
|
-
return data;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return data.filter(function filterItem(item) {
|
|
113
|
-
// For multi-select: filter out selected items if showSelectedItems is false
|
|
114
|
-
if (
|
|
115
|
-
options?.selectedItems &&
|
|
116
|
-
!options.showSelectedItems &&
|
|
117
|
-
options.selectedItems.includes(item)
|
|
118
|
-
) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Get the pipe-delimited search string for this item
|
|
123
|
-
const searchString = itemToSearchString(item);
|
|
124
|
-
// Split on '|' and check if any part matches the input value
|
|
125
|
-
const searchableParts = searchString
|
|
126
|
-
.split("|")
|
|
127
|
-
.map((part) => part.toLowerCase().trim());
|
|
128
|
-
|
|
129
|
-
return searchableParts.some((part) => part.includes(lowerCasedInputValue));
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function groupItems<T extends DataWithId>(
|
|
134
|
-
items: T[],
|
|
135
|
-
groupBy: (item: T) => string,
|
|
136
|
-
allData?: T[]
|
|
137
|
-
): Map<string, T[]> {
|
|
138
|
-
if (items.length === 0) {
|
|
139
|
-
return new Map<string, T[]>();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Determine group order from allData if provided, otherwise use items
|
|
143
|
-
const dataForOrder = allData || items;
|
|
144
|
-
const groupOrder: string[] = [];
|
|
145
|
-
const seenGroups = new Set<string>();
|
|
146
|
-
|
|
147
|
-
// First pass: determine group order from allData
|
|
148
|
-
for (const item of dataForOrder) {
|
|
149
|
-
let groupKey = groupBy(item);
|
|
150
|
-
if (item.id === "clear-selection") {
|
|
151
|
-
continue; // Skip placeholder item for group order
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!seenGroups.has(groupKey)) {
|
|
155
|
-
seenGroups.add(groupKey);
|
|
156
|
-
groupOrder.push(groupKey);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Group filtered items by the groupBy function
|
|
161
|
-
const grouped = new Map<string, T[]>();
|
|
162
|
-
for (const item of items) {
|
|
163
|
-
let groupKey = groupBy(item);
|
|
164
|
-
if (item.id === "clear-selection") {
|
|
165
|
-
continue; // Skip placeholder item for grouping
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!grouped.has(groupKey)) {
|
|
169
|
-
grouped.set(groupKey, []);
|
|
170
|
-
}
|
|
171
|
-
grouped.get(groupKey)!.push(item);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Sort the groups by the group order
|
|
175
|
-
const sortedGrouped = new Map<string, T[]>();
|
|
176
|
-
for (const groupKey of groupOrder) {
|
|
177
|
-
const groupItems = grouped.get(groupKey);
|
|
178
|
-
if (groupItems) {
|
|
179
|
-
sortedGrouped.set(groupKey, groupItems);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return sortedGrouped;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function groupItemsWithHeadings<T extends DataWithId>(
|
|
187
|
-
items: T[],
|
|
188
|
-
groupBy: (item: T) => string,
|
|
189
|
-
allData?: T[],
|
|
190
|
-
selectHeadings = false
|
|
191
|
-
): GroupedItem<T>[] {
|
|
192
|
-
if (items.length === 0) {
|
|
193
|
-
return [];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const grouped = groupItems(items, groupBy, allData);
|
|
197
|
-
|
|
198
|
-
// Create the result array with headings and items in the determined order
|
|
199
|
-
// Only include groups that have items in the filtered items
|
|
200
|
-
const result: GroupedItem<T>[] = [];
|
|
201
|
-
for (const groupKey of grouped.keys()) {
|
|
202
|
-
const groupItems = grouped.get(groupKey);
|
|
203
|
-
// Only include groups that have items in the filtered data
|
|
204
|
-
if (groupItems && groupItems.length > 0) {
|
|
205
|
-
// Add heading
|
|
206
|
-
if (selectHeadings) {
|
|
207
|
-
result.push({
|
|
208
|
-
id: `heading-${groupKey}`,
|
|
209
|
-
type: "heading",
|
|
210
|
-
label: groupKey,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
// Add items in this group
|
|
214
|
-
result.push(...groupItems);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return result;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Creates a wrapper function for itemToString that handles ToggleItem and GroupHeading separately.
|
|
223
|
-
* These items are not selectable (only type T items can be selected), so they use their label property directly.
|
|
224
|
-
*/
|
|
225
|
-
export function createItemToStringForDownshift<T extends DataWithId>(
|
|
226
|
-
itemToString: (item: T | null) => string
|
|
227
|
-
) {
|
|
228
|
-
return (item: T | ToggleItem | GroupHeading | null): string => {
|
|
229
|
-
if (!item) return "";
|
|
230
|
-
if (isToggleItem(item)) {
|
|
231
|
-
return item.label;
|
|
232
|
-
}
|
|
233
|
-
if (isGroupHeading(item)) {
|
|
234
|
-
return item.label;
|
|
235
|
-
}
|
|
236
|
-
return itemToString(item);
|
|
237
|
-
};
|
|
238
|
-
}
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import { useRef, useEffect } from "react";
|
|
2
|
-
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
3
|
-
import type { ReactNode, MouseEvent } from "react";
|
|
4
|
-
import { StyledMenuContent } from "../../MenuContent/styles";
|
|
5
|
-
import {
|
|
6
|
-
MenuItem,
|
|
7
|
-
MenuHeading,
|
|
8
|
-
isGroupHeading,
|
|
9
|
-
isToggleItem,
|
|
10
|
-
type DataWithId,
|
|
11
|
-
type GroupedItem,
|
|
12
|
-
type ToggleItem,
|
|
13
|
-
} from "../Common-V2";
|
|
14
|
-
import type { InputType } from "../../MenuItem/MenuItemTypes";
|
|
15
|
-
|
|
16
|
-
interface AutocompleteContentProps<T extends DataWithId> {
|
|
17
|
-
items: GroupedItem<T>[];
|
|
18
|
-
getMenuProps: any;
|
|
19
|
-
getItemProps: any;
|
|
20
|
-
selectedItems: T[];
|
|
21
|
-
highlightedIndex: number;
|
|
22
|
-
renderItem: (item: T) => ReactNode;
|
|
23
|
-
renderGroupHeading?: (label: string) => string;
|
|
24
|
-
EmptyState?: ReactNode;
|
|
25
|
-
itemToKey: (item: T) => string | number;
|
|
26
|
-
itemToString: (item: T | null) => string;
|
|
27
|
-
inputType: InputType;
|
|
28
|
-
selectHeadings?: boolean;
|
|
29
|
-
onHeadingClick?: (groupLabel: string, allSelected: boolean) => void;
|
|
30
|
-
groupBy?: (item: T) => string;
|
|
31
|
-
allData?: T[];
|
|
32
|
-
selectAllItem?: ToggleItem;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const VirtualizedAutocompleteContent = <T extends DataWithId>({
|
|
36
|
-
items,
|
|
37
|
-
getMenuProps,
|
|
38
|
-
getItemProps,
|
|
39
|
-
selectedItems,
|
|
40
|
-
highlightedIndex,
|
|
41
|
-
renderItem,
|
|
42
|
-
renderGroupHeading,
|
|
43
|
-
EmptyState,
|
|
44
|
-
itemToKey,
|
|
45
|
-
itemToString,
|
|
46
|
-
inputType,
|
|
47
|
-
selectHeadings = false,
|
|
48
|
-
onHeadingClick,
|
|
49
|
-
groupBy,
|
|
50
|
-
allData,
|
|
51
|
-
selectAllItem,
|
|
52
|
-
}: AutocompleteContentProps<T>) => {
|
|
53
|
-
const containerRef = useRef(null);
|
|
54
|
-
|
|
55
|
-
// Check if there are any actual items (not just headings)
|
|
56
|
-
const hasItems = items.some((item) => !isGroupHeading(item));
|
|
57
|
-
|
|
58
|
-
const rowVirtualizer = useVirtualizer({
|
|
59
|
-
count: items.length,
|
|
60
|
-
getScrollElement: () => containerRef.current,
|
|
61
|
-
estimateSize: (index) => {
|
|
62
|
-
const item = items[index];
|
|
63
|
-
return isGroupHeading(item) ? 30 : 35;
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (
|
|
69
|
-
highlightedIndex !== undefined &&
|
|
70
|
-
highlightedIndex !== null &&
|
|
71
|
-
highlightedIndex >= 0
|
|
72
|
-
) {
|
|
73
|
-
rowVirtualizer.scrollToIndex(highlightedIndex);
|
|
74
|
-
}
|
|
75
|
-
}, [highlightedIndex, rowVirtualizer]);
|
|
76
|
-
|
|
77
|
-
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
78
|
-
|
|
79
|
-
const menuProps = getMenuProps({ ref: containerRef });
|
|
80
|
-
return (
|
|
81
|
-
<div {...menuProps} style={{ maxHeight: "400px", overflow: "auto" }}>
|
|
82
|
-
{!hasItems && EmptyState ? (
|
|
83
|
-
<div style={{ padding: "16px" }}>{EmptyState}</div>
|
|
84
|
-
) : (
|
|
85
|
-
<StyledMenuContent
|
|
86
|
-
style={{
|
|
87
|
-
height: `${rowVirtualizer.getTotalSize()}px`,
|
|
88
|
-
position: "relative",
|
|
89
|
-
}}
|
|
90
|
-
>
|
|
91
|
-
<div
|
|
92
|
-
style={{
|
|
93
|
-
position: "absolute",
|
|
94
|
-
top: 0,
|
|
95
|
-
left: 0,
|
|
96
|
-
width: "100%",
|
|
97
|
-
transform: `translateY(${virtualItems[0]?.start ?? 0}px)`,
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
{virtualItems.map((virtualItem) => {
|
|
101
|
-
const item = items[virtualItem.index];
|
|
102
|
-
|
|
103
|
-
if (isGroupHeading(item)) {
|
|
104
|
-
// Find all items in this group from the full data array (if groupBy is provided)
|
|
105
|
-
const groupItems =
|
|
106
|
-
groupBy && allData
|
|
107
|
-
? allData.filter(
|
|
108
|
-
(dataItem) => groupBy(dataItem) === item.label
|
|
109
|
-
)
|
|
110
|
-
: [];
|
|
111
|
-
|
|
112
|
-
// Check if all items in the group are selected
|
|
113
|
-
const allSelected =
|
|
114
|
-
groupItems.length > 0 &&
|
|
115
|
-
groupItems.every((groupItem) =>
|
|
116
|
-
selectedItems.includes(groupItem)
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const headingItemProps = selectHeadings
|
|
120
|
-
? getItemProps({ index: virtualItem.index })
|
|
121
|
-
: {};
|
|
122
|
-
const handleHeadingClick = (e: MouseEvent) => {
|
|
123
|
-
// Call Downshift's onClick if it exists
|
|
124
|
-
headingItemProps.onClick?.(e);
|
|
125
|
-
// Then call our custom handler
|
|
126
|
-
if (onHeadingClick) {
|
|
127
|
-
onHeadingClick(item.label, allSelected);
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<MenuHeading
|
|
133
|
-
key={virtualItem.key}
|
|
134
|
-
label={item.label}
|
|
135
|
-
{...headingItemProps}
|
|
136
|
-
$highlighted={highlightedIndex === virtualItem.index}
|
|
137
|
-
renderGroupHeading={renderGroupHeading}
|
|
138
|
-
selectable={selectHeadings}
|
|
139
|
-
selected={allSelected}
|
|
140
|
-
id={item.id}
|
|
141
|
-
onClick={selectHeadings ? handleHeadingClick : undefined}
|
|
142
|
-
style={{
|
|
143
|
-
width: "100%",
|
|
144
|
-
}}
|
|
145
|
-
/>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Handle ToggleItem separately
|
|
150
|
-
if (isToggleItem(item)) {
|
|
151
|
-
const toggleItem = item as ToggleItem;
|
|
152
|
-
const isItemSelected =
|
|
153
|
-
selectAllItem && toggleItem.id === selectAllItem.id
|
|
154
|
-
? allData &&
|
|
155
|
-
allData.length > 0 &&
|
|
156
|
-
allData.every((item) => selectedItems.includes(item))
|
|
157
|
-
: false;
|
|
158
|
-
|
|
159
|
-
return (
|
|
160
|
-
<MenuItem
|
|
161
|
-
key={virtualItem.key}
|
|
162
|
-
inputType={inputType}
|
|
163
|
-
selected={isItemSelected}
|
|
164
|
-
id={toggleItem.id}
|
|
165
|
-
{...getItemProps({ index: virtualItem.index })}
|
|
166
|
-
style={{
|
|
167
|
-
width: "100%",
|
|
168
|
-
/* height: `${virtualItem.size}px`,
|
|
169
|
-
transform: `translateY(${virtualItem.start}px)`, */
|
|
170
|
-
}}
|
|
171
|
-
$highlighted={highlightedIndex === virtualItem.index}
|
|
172
|
-
$active={false}
|
|
173
|
-
>
|
|
174
|
-
{toggleItem.label}
|
|
175
|
-
</MenuItem>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const dataItem = item as T;
|
|
180
|
-
const isItemSelected = selectedItems.includes(dataItem);
|
|
181
|
-
return (
|
|
182
|
-
<MenuItem
|
|
183
|
-
key={virtualItem.key}
|
|
184
|
-
inputType={inputType}
|
|
185
|
-
selected={isItemSelected}
|
|
186
|
-
id={itemToKey(dataItem)}
|
|
187
|
-
{...getItemProps({ index: virtualItem.index })}
|
|
188
|
-
style={{
|
|
189
|
-
width: "100%",
|
|
190
|
-
}}
|
|
191
|
-
$highlighted={highlightedIndex === virtualItem.index}
|
|
192
|
-
$active={false}
|
|
193
|
-
>
|
|
194
|
-
{renderItem(dataItem)}
|
|
195
|
-
</MenuItem>
|
|
196
|
-
);
|
|
197
|
-
})}
|
|
198
|
-
</div>
|
|
199
|
-
</StyledMenuContent>
|
|
200
|
-
)}
|
|
201
|
-
</div>
|
|
202
|
-
);
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
export const NonVirtualizedAutocompleteContent = <T extends DataWithId>({
|
|
206
|
-
items,
|
|
207
|
-
getMenuProps,
|
|
208
|
-
getItemProps,
|
|
209
|
-
selectedItems,
|
|
210
|
-
highlightedIndex,
|
|
211
|
-
renderItem,
|
|
212
|
-
renderGroupHeading,
|
|
213
|
-
EmptyState,
|
|
214
|
-
itemToKey,
|
|
215
|
-
itemToString,
|
|
216
|
-
inputType,
|
|
217
|
-
selectHeadings = false,
|
|
218
|
-
onHeadingClick,
|
|
219
|
-
groupBy,
|
|
220
|
-
allData,
|
|
221
|
-
selectAllItem,
|
|
222
|
-
}: AutocompleteContentProps<T>) => {
|
|
223
|
-
// Check if there are any actual items (not just headings)
|
|
224
|
-
const hasItems = items.some((item) => !isGroupHeading(item));
|
|
225
|
-
|
|
226
|
-
const menuProps = getMenuProps();
|
|
227
|
-
return (
|
|
228
|
-
<div {...menuProps} style={{ maxHeight: "400px", overflow: "auto" }}>
|
|
229
|
-
{!hasItems && EmptyState ? (
|
|
230
|
-
<div style={{ padding: "16px" }}>{EmptyState}</div>
|
|
231
|
-
) : (
|
|
232
|
-
<StyledMenuContent
|
|
233
|
-
style={{
|
|
234
|
-
position: "relative",
|
|
235
|
-
}}
|
|
236
|
-
>
|
|
237
|
-
{items.map((item, index) => {
|
|
238
|
-
if (isGroupHeading(item)) {
|
|
239
|
-
// Find all items in this group from the full data array (if groupBy is provided)
|
|
240
|
-
const groupItems =
|
|
241
|
-
groupBy && allData
|
|
242
|
-
? allData.filter(
|
|
243
|
-
(dataItem) => groupBy(dataItem) === item.label
|
|
244
|
-
)
|
|
245
|
-
: [];
|
|
246
|
-
|
|
247
|
-
// Check if all items in the group are selected
|
|
248
|
-
const allSelected =
|
|
249
|
-
groupItems.length > 0 &&
|
|
250
|
-
groupItems.every((groupItem) =>
|
|
251
|
-
selectedItems.includes(groupItem)
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
const headingItemProps = selectHeadings
|
|
255
|
-
? getItemProps({ index })
|
|
256
|
-
: {};
|
|
257
|
-
const handleHeadingClick = (e: MouseEvent) => {
|
|
258
|
-
// Call Downshift's onClick if it exists
|
|
259
|
-
headingItemProps.onClick?.(e);
|
|
260
|
-
// Then call our custom handler
|
|
261
|
-
if (onHeadingClick) {
|
|
262
|
-
onHeadingClick(item.label, allSelected);
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<MenuHeading
|
|
268
|
-
key={index}
|
|
269
|
-
{...headingItemProps}
|
|
270
|
-
$highlighted={highlightedIndex === index}
|
|
271
|
-
label={item.label}
|
|
272
|
-
renderGroupHeading={renderGroupHeading}
|
|
273
|
-
selectable={selectHeadings}
|
|
274
|
-
selected={allSelected}
|
|
275
|
-
id={item.id}
|
|
276
|
-
onClick={selectHeadings ? handleHeadingClick : undefined}
|
|
277
|
-
/>
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Handle ToggleItem separately
|
|
282
|
-
if (isToggleItem(item)) {
|
|
283
|
-
const toggleItem = item as ToggleItem;
|
|
284
|
-
const isItemSelected =
|
|
285
|
-
selectAllItem && toggleItem.id === selectAllItem.id
|
|
286
|
-
? allData &&
|
|
287
|
-
allData.length > 0 &&
|
|
288
|
-
allData.every((item) => selectedItems.includes(item))
|
|
289
|
-
: false;
|
|
290
|
-
return (
|
|
291
|
-
<MenuItem
|
|
292
|
-
key={index}
|
|
293
|
-
{...getItemProps({ index })}
|
|
294
|
-
$highlighted={highlightedIndex === index}
|
|
295
|
-
$active={false}
|
|
296
|
-
inputType={inputType}
|
|
297
|
-
selected={isItemSelected}
|
|
298
|
-
id={toggleItem.id}
|
|
299
|
-
>
|
|
300
|
-
{toggleItem.label}
|
|
301
|
-
</MenuItem>
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const dataItem = item as T;
|
|
306
|
-
const isItemSelected = selectedItems.includes(dataItem);
|
|
307
|
-
return (
|
|
308
|
-
<MenuItem
|
|
309
|
-
key={index}
|
|
310
|
-
{...getItemProps({ index })}
|
|
311
|
-
$highlighted={highlightedIndex === index}
|
|
312
|
-
$active={false}
|
|
313
|
-
inputType={inputType}
|
|
314
|
-
selected={isItemSelected}
|
|
315
|
-
id={itemToKey(dataItem)}
|
|
316
|
-
>
|
|
317
|
-
{renderItem(dataItem)}
|
|
318
|
-
</MenuItem>
|
|
319
|
-
);
|
|
320
|
-
})}
|
|
321
|
-
</StyledMenuContent>
|
|
322
|
-
)}
|
|
323
|
-
</div>
|
|
324
|
-
);
|
|
325
|
-
};
|