@sproutsocial/seeds-react-menu 1.11.8 → 1.12.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 +20 -20
- package/CHANGELOG.md +12 -0
- package/dist/esm/index.js +5 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v3/index.js +12 -4
- package/dist/esm/v3/index.js.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/v3/index.d.mts +4 -0
- package/dist/v3/index.d.ts +4 -0
- package/dist/v3/index.js +12 -4
- package/dist/v3/index.js.map +1 -1
- package/package.json +1 -1
- package/src/MenuGroup/MenuGroup.tsx +2 -2
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +4 -4
- package/src/MenuSearchInput/MenuSearchInput.tsx +3 -1
- package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +60 -0
- package/src/v3/MultiCombobox.stories.tsx +27 -0
- package/src/v3/MultiCombobox.tsx +3 -0
- package/src/v3/MultiSearchableSelect.stories.tsx +28 -0
- package/src/v3/MultiSearchableSelect.tsx +3 -0
- package/src/v3/SingleCombobox.stories.tsx +27 -0
- package/src/v3/SingleCombobox.tsx +3 -0
- package/src/v3/SingleSearchableSelect.stories.tsx +28 -0
- package/src/v3/SingleSearchableSelect.tsx +3 -0
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ import { mapMenuItems } from "../hooks/useMenuChildren";
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
const MenuGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
29
|
-
titleAs = "
|
|
29
|
+
titleAs = "div",
|
|
30
30
|
children: childrenProp,
|
|
31
31
|
title,
|
|
32
32
|
color,
|
|
@@ -68,7 +68,7 @@ const MenuGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
68
68
|
{...rest}
|
|
69
69
|
>
|
|
70
70
|
{title && (
|
|
71
|
-
<StyledTitle
|
|
71
|
+
<StyledTitle as={titleAs} id={titleId}>
|
|
72
72
|
{title}
|
|
73
73
|
</StyledTitle>
|
|
74
74
|
)}
|
|
@@ -199,12 +199,12 @@ describe("MenuGroup", () => {
|
|
|
199
199
|
</SingleSelectMenu>
|
|
200
200
|
);
|
|
201
201
|
|
|
202
|
-
//
|
|
202
|
+
// The group is labelled via aria-labelledby pointing to the title div.
|
|
203
|
+
// The title must not carry role="heading" since heading is not an allowed
|
|
204
|
+
// descendant of listbox/menu (axe: aria-allowed-attr).
|
|
203
205
|
const menuGroup = screen.getByRole("group", { name: menuTitle });
|
|
204
206
|
expect(menuGroup).toBeInTheDocument();
|
|
205
|
-
expect(
|
|
206
|
-
screen.queryByRole("heading", { name: menuTitle })
|
|
207
|
-
).toBeInTheDocument();
|
|
207
|
+
expect(screen.queryByRole("heading")).not.toBeInTheDocument();
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
it("does not place role=group on an <li> (axe aria-allowed-role)", () => {
|
|
@@ -26,6 +26,7 @@ export const MenuSearchInput = ({
|
|
|
26
26
|
getIsItemVisible,
|
|
27
27
|
inputProps,
|
|
28
28
|
onChange,
|
|
29
|
+
"aria-label": ariaLabel,
|
|
29
30
|
...restInputProps
|
|
30
31
|
}: TypeMenuSearchInputProps) => {
|
|
31
32
|
const {
|
|
@@ -69,8 +70,9 @@ export const MenuSearchInput = ({
|
|
|
69
70
|
// apply onKeyDown logic for downshift keyboard accessibility support
|
|
70
71
|
onKeyDown={handleKeyDown}
|
|
71
72
|
autoComplete={false}
|
|
72
|
-
|
|
73
|
+
ariaLabel={ariaLabel}
|
|
73
74
|
inputProps={{
|
|
75
|
+
["aria-autocomplete"]: "list",
|
|
74
76
|
["aria-activedescendant"]: toggleButtonProps["aria-activedescendant"],
|
|
75
77
|
["aria-controls"]: toggleButtonProps["aria-controls"],
|
|
76
78
|
...inputProps,
|
|
@@ -275,6 +275,66 @@ export const SingleSelectMenuWithCustomEmpty: Story = {
|
|
|
275
275
|
},
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
+
const russianBooks = TEST_BOOKS.filter(({ author }) =>
|
|
279
|
+
[
|
|
280
|
+
"Leo Tolstoy",
|
|
281
|
+
"Fyodor Dostoyevsy",
|
|
282
|
+
"Fyodor Dostoevsky",
|
|
283
|
+
"Lev Tolstoy",
|
|
284
|
+
].includes(author)
|
|
285
|
+
);
|
|
286
|
+
const worldBooks = TEST_BOOKS.filter(
|
|
287
|
+
({ author }) =>
|
|
288
|
+
![
|
|
289
|
+
"Leo Tolstoy",
|
|
290
|
+
"Fyodor Dostoyevsy",
|
|
291
|
+
"Fyodor Dostoevsky",
|
|
292
|
+
"Lev Tolstoy",
|
|
293
|
+
].includes(author)
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
export const SingleSelectMenuWithSearchAndGroups: Story = {
|
|
297
|
+
name: "Single Select Menu with Search and Groups",
|
|
298
|
+
render: ({ inputType, ...args }) => {
|
|
299
|
+
return (
|
|
300
|
+
<SingleSelectMenu
|
|
301
|
+
itemToString={(item) => {
|
|
302
|
+
if (!item) return "";
|
|
303
|
+
return TEST_BOOKS.find(({ id }) => id === item.id)?.title ?? "";
|
|
304
|
+
}}
|
|
305
|
+
menuToggleElement={<StorybookMenuToggleButton />}
|
|
306
|
+
onStateChange={(changes) => action("onStateChange")(changes)}
|
|
307
|
+
{...(args as object)}
|
|
308
|
+
>
|
|
309
|
+
<MenuHeader title="Select Book">
|
|
310
|
+
<MenuSearchInput
|
|
311
|
+
id="single-select-search-groups"
|
|
312
|
+
name="search"
|
|
313
|
+
type="search"
|
|
314
|
+
aria-label="Search Books"
|
|
315
|
+
/>
|
|
316
|
+
</MenuHeader>
|
|
317
|
+
<MenuContent>
|
|
318
|
+
<MenuGroup id="russian-lit" title="Russian Literature">
|
|
319
|
+
{russianBooks.map(({ id, title }) => (
|
|
320
|
+
<MenuItem key={id} id={id} inputType={inputType}>
|
|
321
|
+
{title}
|
|
322
|
+
</MenuItem>
|
|
323
|
+
))}
|
|
324
|
+
</MenuGroup>
|
|
325
|
+
<MenuGroup id="world-lit" title="World Literature">
|
|
326
|
+
{worldBooks.map(({ id, title }) => (
|
|
327
|
+
<MenuItem key={id} id={id} inputType={inputType}>
|
|
328
|
+
{title}
|
|
329
|
+
</MenuItem>
|
|
330
|
+
))}
|
|
331
|
+
</MenuGroup>
|
|
332
|
+
</MenuContent>
|
|
333
|
+
</SingleSelectMenu>
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
278
338
|
export const SingleSelectMenuSmallSize: Story = {
|
|
279
339
|
name: "Small Size",
|
|
280
340
|
render: ({ inputType, ...args }) => {
|
|
@@ -344,3 +344,30 @@ export const VirtualizedSelectAll: Story = {
|
|
|
344
344
|
</div>
|
|
345
345
|
),
|
|
346
346
|
};
|
|
347
|
+
|
|
348
|
+
export const CustomSearch: Story = {
|
|
349
|
+
name: "Custom search (title + author)",
|
|
350
|
+
render: () => (
|
|
351
|
+
<div style={{ width: "300px" }}>
|
|
352
|
+
<FormField label="Pick books">
|
|
353
|
+
{({ id }) => (
|
|
354
|
+
<MultiCombobox
|
|
355
|
+
id={id}
|
|
356
|
+
data={books}
|
|
357
|
+
itemToString={itemToString}
|
|
358
|
+
renderItem={renderItem}
|
|
359
|
+
placeholder="Search by title or author..."
|
|
360
|
+
filter={(item, query) => {
|
|
361
|
+
const book = item as (typeof books)[number];
|
|
362
|
+
const q = query.toLowerCase();
|
|
363
|
+
return (
|
|
364
|
+
book.title.toLowerCase().includes(q) ||
|
|
365
|
+
book.author.toLowerCase().includes(q)
|
|
366
|
+
);
|
|
367
|
+
}}
|
|
368
|
+
/>
|
|
369
|
+
)}
|
|
370
|
+
</FormField>
|
|
371
|
+
</div>
|
|
372
|
+
),
|
|
373
|
+
};
|
package/src/v3/MultiCombobox.tsx
CHANGED
|
@@ -65,6 +65,7 @@ interface MultiComboboxBaseProps {
|
|
|
65
65
|
isLoading?: boolean;
|
|
66
66
|
loadingText?: string;
|
|
67
67
|
disabled?: boolean;
|
|
68
|
+
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
interface MultiComboboxDataProps<T extends DataWithId>
|
|
@@ -125,6 +126,7 @@ export function MultiCombobox<T extends DataWithId>(
|
|
|
125
126
|
emptyText = "No results found.",
|
|
126
127
|
isLoading = false,
|
|
127
128
|
loadingText = "Loading...",
|
|
129
|
+
filter,
|
|
128
130
|
} = props;
|
|
129
131
|
|
|
130
132
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -378,6 +380,7 @@ export function MultiCombobox<T extends DataWithId>(
|
|
|
378
380
|
open={open}
|
|
379
381
|
disabled={props.disabled}
|
|
380
382
|
onOpenChange={setOpen}
|
|
383
|
+
filter={filter}
|
|
381
384
|
onItemHighlighted={
|
|
382
385
|
isVirtualized
|
|
383
386
|
? (item, { reason, index }) => {
|
|
@@ -412,3 +412,31 @@ export const LoadingWithToggle: Story = {
|
|
|
412
412
|
);
|
|
413
413
|
},
|
|
414
414
|
};
|
|
415
|
+
|
|
416
|
+
export const CustomSearch: Story = {
|
|
417
|
+
name: "Custom search (title + author)",
|
|
418
|
+
render: () => (
|
|
419
|
+
<div style={{ width: "300px" }}>
|
|
420
|
+
<FormField label="Pick books">
|
|
421
|
+
{({ id }) => (
|
|
422
|
+
<MultiSearchableSelect
|
|
423
|
+
id={id}
|
|
424
|
+
data={books}
|
|
425
|
+
itemToString={itemToString}
|
|
426
|
+
renderItem={renderItem}
|
|
427
|
+
placeholder="Select books"
|
|
428
|
+
searchPlaceholder="Search by title or author..."
|
|
429
|
+
filter={(item, query) => {
|
|
430
|
+
const book = item as (typeof books)[number];
|
|
431
|
+
const q = query.toLowerCase();
|
|
432
|
+
return (
|
|
433
|
+
book.title.toLowerCase().includes(q) ||
|
|
434
|
+
book.author.toLowerCase().includes(q)
|
|
435
|
+
);
|
|
436
|
+
}}
|
|
437
|
+
/>
|
|
438
|
+
)}
|
|
439
|
+
</FormField>
|
|
440
|
+
</div>
|
|
441
|
+
),
|
|
442
|
+
};
|
|
@@ -72,6 +72,7 @@ interface MultiSearchableSelectBaseProps {
|
|
|
72
72
|
isLoading?: boolean;
|
|
73
73
|
loadingText?: string;
|
|
74
74
|
disabled?: boolean;
|
|
75
|
+
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
interface MultiSearchableSelectDataProps<T extends DataWithId>
|
|
@@ -132,6 +133,7 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
132
133
|
emptyText = "No results found.",
|
|
133
134
|
isLoading = false,
|
|
134
135
|
loadingText = "Loading...",
|
|
136
|
+
filter,
|
|
135
137
|
} = props;
|
|
136
138
|
|
|
137
139
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -365,6 +367,7 @@ export function MultiSearchableSelect<T extends DataWithId>(
|
|
|
365
367
|
multiple
|
|
366
368
|
id={id}
|
|
367
369
|
virtualized={isVirtualized}
|
|
370
|
+
filter={filter}
|
|
368
371
|
open={open}
|
|
369
372
|
disabled={props.disabled}
|
|
370
373
|
onOpenChange={setOpen}
|
|
@@ -191,3 +191,30 @@ export const Virtualized: Story = {
|
|
|
191
191
|
</div>
|
|
192
192
|
),
|
|
193
193
|
};
|
|
194
|
+
|
|
195
|
+
export const CustomSearch: Story = {
|
|
196
|
+
name: "Custom search (title + author)",
|
|
197
|
+
render: () => (
|
|
198
|
+
<div style={{ width: "300px" }}>
|
|
199
|
+
<FormField label="Pick a book">
|
|
200
|
+
{({ id }) => (
|
|
201
|
+
<SingleCombobox
|
|
202
|
+
id={id}
|
|
203
|
+
data={books}
|
|
204
|
+
itemToString={itemToString}
|
|
205
|
+
renderItem={renderItem}
|
|
206
|
+
placeholder="Search by title or author..."
|
|
207
|
+
filter={(item, query) => {
|
|
208
|
+
const book = item as (typeof books)[number];
|
|
209
|
+
const q = query.toLowerCase();
|
|
210
|
+
return (
|
|
211
|
+
book.title.toLowerCase().includes(q) ||
|
|
212
|
+
book.author.toLowerCase().includes(q)
|
|
213
|
+
);
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
)}
|
|
217
|
+
</FormField>
|
|
218
|
+
</div>
|
|
219
|
+
),
|
|
220
|
+
};
|
|
@@ -30,6 +30,7 @@ interface SingleComboboxBaseProps {
|
|
|
30
30
|
placeholder?: string;
|
|
31
31
|
emptyText?: string;
|
|
32
32
|
disabled?: boolean;
|
|
33
|
+
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
interface SingleComboboxDataBaseProps<T extends DataWithId>
|
|
@@ -86,6 +87,7 @@ export function SingleCombobox<T extends DataWithId>(
|
|
|
86
87
|
id,
|
|
87
88
|
placeholder = "Search...",
|
|
88
89
|
emptyText = "No results found.",
|
|
90
|
+
filter,
|
|
89
91
|
} = props;
|
|
90
92
|
|
|
91
93
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -177,6 +179,7 @@ export function SingleCombobox<T extends DataWithId>(
|
|
|
177
179
|
}
|
|
178
180
|
virtualized={isVirtualized}
|
|
179
181
|
disabled={props.disabled}
|
|
182
|
+
filter={filter}
|
|
180
183
|
open={open}
|
|
181
184
|
onOpenChange={setOpen}
|
|
182
185
|
onItemHighlighted={
|
|
@@ -303,3 +303,31 @@ export const LoadingWithToggle: Story = {
|
|
|
303
303
|
);
|
|
304
304
|
},
|
|
305
305
|
};
|
|
306
|
+
|
|
307
|
+
export const CustomSearch: Story = {
|
|
308
|
+
name: "Custom search (title + author)",
|
|
309
|
+
render: () => (
|
|
310
|
+
<div style={{ width: "300px" }}>
|
|
311
|
+
<FormField label="Pick a book">
|
|
312
|
+
{({ id }) => (
|
|
313
|
+
<SingleSearchableSelect
|
|
314
|
+
id={id}
|
|
315
|
+
data={books}
|
|
316
|
+
itemToString={itemToString}
|
|
317
|
+
renderItem={renderItem}
|
|
318
|
+
placeholder="Select a book"
|
|
319
|
+
searchPlaceholder="Search by title or author..."
|
|
320
|
+
filter={(item, query) => {
|
|
321
|
+
const book = item as (typeof books)[number];
|
|
322
|
+
const q = query.toLowerCase();
|
|
323
|
+
return (
|
|
324
|
+
book.title.toLowerCase().includes(q) ||
|
|
325
|
+
book.author.toLowerCase().includes(q)
|
|
326
|
+
);
|
|
327
|
+
}}
|
|
328
|
+
/>
|
|
329
|
+
)}
|
|
330
|
+
</FormField>
|
|
331
|
+
</div>
|
|
332
|
+
),
|
|
333
|
+
};
|
|
@@ -40,6 +40,7 @@ interface SingleSearchableSelectBaseProps {
|
|
|
40
40
|
isLoading?: boolean;
|
|
41
41
|
loadingText?: string;
|
|
42
42
|
disabled?: boolean;
|
|
43
|
+
filter?: ((item: unknown, query: string) => boolean) | null;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
interface SingleSearchableSelectDataBaseProps<T extends DataWithId>
|
|
@@ -103,6 +104,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
103
104
|
emptyText = "No results found.",
|
|
104
105
|
isLoading = false,
|
|
105
106
|
loadingText = "Loading...",
|
|
107
|
+
filter,
|
|
106
108
|
} = props;
|
|
107
109
|
|
|
108
110
|
const isChildrenMode = "children" in props && props.children != null;
|
|
@@ -210,6 +212,7 @@ export function SingleSearchableSelect<T extends DataWithId>(
|
|
|
210
212
|
: groups ?? (isChildrenMode ? undefined : data)
|
|
211
213
|
}
|
|
212
214
|
virtualized={isVirtualized}
|
|
215
|
+
filter={filter}
|
|
213
216
|
open={open}
|
|
214
217
|
onOpenChange={setOpen}
|
|
215
218
|
onItemHighlighted={
|