@sproutsocial/seeds-react-menu 1.11.3 → 1.11.5
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 +10 -10
- package/CHANGELOG.md +18 -0
- package/dist/esm/v3/index.js +44 -9
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/v3/index.js +44 -9
- package/dist/v3/index.js.map +1 -1
- package/package.json +9 -9
- package/src/v3/Common/SelectStyles.tsx +1 -0
- package/src/v3/Common/VirtualizedComboboxList.tsx +23 -1
- package/src/v3/Common/sentinels.ts +17 -0
- package/src/v3/SingleSearchableSelect.stories.tsx +21 -0
- package/src/v3/SingleSearchableSelect.tsx +32 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sproutsocial/seeds-react-menu",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.5",
|
|
4
4
|
"description": "Seeds React Menu",
|
|
5
5
|
"author": "Sprout Social, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,17 +37,17 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@radix-ui/react-popover": "^1.1.2",
|
|
39
39
|
"@sproutsocial/seeds-react-box": "^1.1.16",
|
|
40
|
-
"@sproutsocial/seeds-react-button": "^2.0.
|
|
41
|
-
"@sproutsocial/seeds-react-checkbox": "^1.3.
|
|
42
|
-
"@sproutsocial/seeds-react-icon": "^2.3.
|
|
43
|
-
"@sproutsocial/seeds-react-input": "^1.5.
|
|
40
|
+
"@sproutsocial/seeds-react-button": "^2.0.2",
|
|
41
|
+
"@sproutsocial/seeds-react-checkbox": "^1.3.30",
|
|
42
|
+
"@sproutsocial/seeds-react-icon": "^2.3.4",
|
|
43
|
+
"@sproutsocial/seeds-react-input": "^1.5.13",
|
|
44
44
|
"@sproutsocial/seeds-react-label": "^1.0.16",
|
|
45
|
-
"@sproutsocial/seeds-react-popout": "^2.4.
|
|
45
|
+
"@sproutsocial/seeds-react-popout": "^2.4.35",
|
|
46
46
|
"@sproutsocial/seeds-react-radio": "^1.3.16",
|
|
47
|
-
"@sproutsocial/seeds-react-switch": "^1.2.
|
|
47
|
+
"@sproutsocial/seeds-react-switch": "^1.2.29",
|
|
48
48
|
"@sproutsocial/seeds-react-system-props": "^3.0.2",
|
|
49
49
|
"@sproutsocial/seeds-react-theme": "^3.6.2",
|
|
50
|
-
"@sproutsocial/seeds-react-token-input": "^1.4.
|
|
50
|
+
"@sproutsocial/seeds-react-token-input": "^1.4.35",
|
|
51
51
|
"@sproutsocial/seeds-react-utilities": "^4.3.0",
|
|
52
52
|
"@base-ui/react": "^1.3.0",
|
|
53
53
|
"@tanstack/react-virtual": "^3.13.12",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"styled-system": "^5.1.5"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@sproutsocial/seeds-react-tooltip": "^1.1.
|
|
60
|
+
"@sproutsocial/seeds-react-tooltip": "^1.1.17",
|
|
61
61
|
"@sproutsocial/eslint-config-seeds": "*",
|
|
62
62
|
"@sproutsocial/seeds-react-testing-library": "*",
|
|
63
63
|
"@sproutsocial/seeds-testing": "*",
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
ComboboxSelectAllItem,
|
|
9
9
|
} from "./ComboboxStyles";
|
|
10
10
|
import type { DataWithId } from "./types";
|
|
11
|
+
import { isPlaceholderSentinel } from "./sentinels";
|
|
11
12
|
|
|
12
13
|
export const ITEM_HEIGHT = 32;
|
|
13
14
|
|
|
@@ -37,6 +38,10 @@ interface VirtualizedComboboxListProps<T extends DataWithId> {
|
|
|
37
38
|
> | null>;
|
|
38
39
|
value: T | T[] | null;
|
|
39
40
|
groups: Array<{ value: string; items: T[] }> | null;
|
|
41
|
+
// `data` contains only real items (no sentinels). Sentinel rows (placeholder,
|
|
42
|
+
// group headers, select-all) come exclusively from `filteredItems` via
|
|
43
|
+
// Combobox.useFilteredItems. This means `data.length` is the correct count
|
|
44
|
+
// of selectable items for select-all logic.
|
|
40
45
|
data: T[];
|
|
41
46
|
itemToString: (item: T) => string;
|
|
42
47
|
inputType: "icon" | "radio" | "checkbox" | "none";
|
|
@@ -109,7 +114,7 @@ export default function VirtualizedComboboxList<T extends DataWithId>({
|
|
|
109
114
|
>
|
|
110
115
|
{virtualizer.getVirtualItems().map((virtualItem) => {
|
|
111
116
|
const row = filteredItems[virtualItem.index];
|
|
112
|
-
if (
|
|
117
|
+
if (row == null) return null;
|
|
113
118
|
|
|
114
119
|
const baseStyle: React.CSSProperties = {
|
|
115
120
|
position: "absolute",
|
|
@@ -120,6 +125,22 @@ export default function VirtualizedComboboxList<T extends DataWithId>({
|
|
|
120
125
|
boxSizing: "border-box",
|
|
121
126
|
};
|
|
122
127
|
|
|
128
|
+
if (isPlaceholderSentinel(row)) {
|
|
129
|
+
return (
|
|
130
|
+
<ComboboxItem
|
|
131
|
+
key="placeholder"
|
|
132
|
+
index={virtualItem.index}
|
|
133
|
+
data-index={virtualItem.index}
|
|
134
|
+
value={row as unknown as T}
|
|
135
|
+
itemId="placeholder"
|
|
136
|
+
label={row.label}
|
|
137
|
+
inputType={inputType}
|
|
138
|
+
style={baseStyle}
|
|
139
|
+
ref={virtualizer.measureElement}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
123
144
|
if (isSelectAllSentinel(row)) {
|
|
124
145
|
const selectedCount = selectedArray.length;
|
|
125
146
|
const totalCount = data.length;
|
|
@@ -185,6 +206,7 @@ export default function VirtualizedComboboxList<T extends DataWithId>({
|
|
|
185
206
|
value={item}
|
|
186
207
|
itemId={String(item.id)}
|
|
187
208
|
label={itemToString(item)}
|
|
209
|
+
disabled={item.disabled}
|
|
188
210
|
inputType={inputType}
|
|
189
211
|
renderItem={
|
|
190
212
|
renderItem ? () => renderItem(item) : () => itemToString(item)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ─── Shared sentinel types & predicates ────────────────────────────────────
|
|
2
|
+
// Sentinels are non-data rows injected into virtualized lists for special UI
|
|
3
|
+
// items (placeholder, group headers, select-all). They carry no `.id` field
|
|
4
|
+
// and must never be counted toward real data totals.
|
|
5
|
+
|
|
6
|
+
export interface PlaceholderSentinel {
|
|
7
|
+
__placeholder: true;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function makePlaceholderSentinel(label: string): PlaceholderSentinel {
|
|
12
|
+
return { __placeholder: true, label };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isPlaceholderSentinel(v: unknown): v is PlaceholderSentinel {
|
|
16
|
+
return typeof v === "object" && v !== null && "__placeholder" in v;
|
|
17
|
+
}
|
|
@@ -208,6 +208,27 @@ export const IncludePlaceholderItem: Story = {
|
|
|
208
208
|
),
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
+
export const IncludePlaceholderItemVirtualized: Story = {
|
|
212
|
+
name: "Include placeholder item (virtualized)",
|
|
213
|
+
render: () => (
|
|
214
|
+
<div style={{ width: "300px" }}>
|
|
215
|
+
<FormField label="Pick a book">
|
|
216
|
+
{({ id }) => (
|
|
217
|
+
<SingleSearchableSelect
|
|
218
|
+
id={id}
|
|
219
|
+
data={largeBookSet}
|
|
220
|
+
itemToString={itemToString}
|
|
221
|
+
placeholder="Select a book"
|
|
222
|
+
searchPlaceholder="Search books..."
|
|
223
|
+
includePlaceholderItem
|
|
224
|
+
isVirtualized
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
</FormField>
|
|
228
|
+
</div>
|
|
229
|
+
),
|
|
230
|
+
};
|
|
231
|
+
|
|
211
232
|
export const Virtualized: Story = {
|
|
212
233
|
name: "Virtualized",
|
|
213
234
|
render: () => (
|
|
@@ -6,6 +6,11 @@ import ComboboxItem from "./Common/ComboboxItem";
|
|
|
6
6
|
import VirtualizedComboboxList from "./Common/VirtualizedComboboxList";
|
|
7
7
|
import type { DataWithId } from "./Common/types";
|
|
8
8
|
import { groupItems } from "./Common/groupItems";
|
|
9
|
+
import type { PlaceholderSentinel } from "./Common/sentinels";
|
|
10
|
+
import {
|
|
11
|
+
makePlaceholderSentinel,
|
|
12
|
+
isPlaceholderSentinel,
|
|
13
|
+
} from "./Common/sentinels";
|
|
9
14
|
import { Field } from "./Common/SelectStyles";
|
|
10
15
|
import {
|
|
11
16
|
ComboboxGroup,
|
|
@@ -164,10 +169,14 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
164
169
|
? idToItem(selectedItemId as T["id"] | null)
|
|
165
170
|
: internalValue;
|
|
166
171
|
|
|
167
|
-
|
|
168
|
-
|
|
172
|
+
type ItemOrSentinel = T | PlaceholderSentinel;
|
|
173
|
+
|
|
174
|
+
function handleValueChange(newItem: ItemOrSentinel | null) {
|
|
175
|
+
const realItem =
|
|
176
|
+
newItem == null || isPlaceholderSentinel(newItem) ? null : newItem;
|
|
177
|
+
if (!isControlled) setInternalValue(realItem);
|
|
169
178
|
if (onSelectedItemIdChange) {
|
|
170
|
-
onSelectedItemIdChange(
|
|
179
|
+
onSelectedItemIdChange(realItem ? realItem.id : null);
|
|
171
180
|
}
|
|
172
181
|
}
|
|
173
182
|
|
|
@@ -183,11 +192,22 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
183
192
|
id={id}
|
|
184
193
|
value={value}
|
|
185
194
|
disabled={props.disabled}
|
|
186
|
-
onValueChange={handleValueChange}
|
|
187
|
-
itemToStringLabel={
|
|
188
|
-
|
|
195
|
+
onValueChange={handleValueChange as (item: T | null) => void}
|
|
196
|
+
itemToStringLabel={(item: T | null) =>
|
|
197
|
+
isPlaceholderSentinel(item) ? item.label : itemToString(item)
|
|
198
|
+
}
|
|
199
|
+
isItemEqualToValue={(a, b) => {
|
|
200
|
+
if (isPlaceholderSentinel(a) && isPlaceholderSentinel(b)) return true;
|
|
201
|
+
if (isPlaceholderSentinel(a)) return b == null;
|
|
202
|
+
if (isPlaceholderSentinel(b)) return a == null;
|
|
203
|
+
return a != null && b != null && a.id === b.id;
|
|
204
|
+
}}
|
|
189
205
|
items={
|
|
190
|
-
isVirtualized
|
|
206
|
+
isVirtualized
|
|
207
|
+
? includePlaceholderItem
|
|
208
|
+
? [makePlaceholderSentinel(placeholder) as unknown as T, ...data]
|
|
209
|
+
: data
|
|
210
|
+
: groups ?? (isChildrenMode ? undefined : data)
|
|
191
211
|
}
|
|
192
212
|
virtualized={isVirtualized}
|
|
193
213
|
open={open}
|
|
@@ -220,7 +240,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
220
240
|
? renderSelection
|
|
221
241
|
? renderSelection(value)
|
|
222
242
|
: itemToString(value)
|
|
223
|
-
:
|
|
243
|
+
: placeholder}
|
|
224
244
|
</Combobox.Value>
|
|
225
245
|
</SearchableSelectPlaceholder>
|
|
226
246
|
<SearchableSelectTriggerIcon>
|
|
@@ -305,9 +325,12 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
305
325
|
{includePlaceholderItem && (
|
|
306
326
|
<ComboboxItem
|
|
307
327
|
key="placeholder"
|
|
308
|
-
value={
|
|
328
|
+
value={
|
|
329
|
+
makePlaceholderSentinel(placeholder) as unknown as T
|
|
330
|
+
}
|
|
309
331
|
itemId="placeholder"
|
|
310
332
|
label={placeholder}
|
|
333
|
+
inputType={inputType}
|
|
311
334
|
/>
|
|
312
335
|
)}
|
|
313
336
|
<Combobox.Collection>
|