@sproutsocial/seeds-react-menu 1.10.7 → 1.10.9
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 +22 -0
- package/dist/esm/index.js +47 -24
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +37 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/MenuGroup/MenuGroup.tsx +16 -3
- package/src/MenuGroup/__tests__/MenuGroup.test.tsx +19 -0
- package/src/MenuItem/MenuItem.tsx +21 -10
- package/src/MenuItem/__tests__/MenuItem.test.tsx +19 -0
- package/src/MenuSearchInput/MenuSearchInput.tsx +2 -0
- package/src/MenuSearchInput/__tests__/MenuSearchInput.test.tsx +23 -0
package/package.json
CHANGED
|
@@ -48,11 +48,19 @@ const MenuGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
50
|
return (
|
|
51
|
+
// role="none" on the outer <li>: this wrapper is purely visual (it exists
|
|
52
|
+
// for the border-bottom divider between groups in the parent menu <ul>).
|
|
53
|
+
// The ARIA group semantics live on the inner <ul> below — axe rejects
|
|
54
|
+
// role="group" on <li>, and a native <ul> here would trigger the `list`
|
|
55
|
+
// rule because its children are <li role="none"> menuitem wrappers.
|
|
56
|
+
//
|
|
57
|
+
// TODO(DS-4273): drop the list-based DOM entirely (MenuContent/MenuGroup
|
|
58
|
+
// as <div>, no <ul>/<li>, no role="none" wrappers). This is the followup
|
|
59
|
+
// to DS-4271's minimal ARIA patch.
|
|
51
60
|
<StyledMenuGroup
|
|
52
|
-
role="
|
|
61
|
+
role="none"
|
|
53
62
|
id={id}
|
|
54
63
|
isSingleSelect={isSingleSelect}
|
|
55
|
-
aria-labelledby={title ? titleId : undefined}
|
|
56
64
|
// TODO: fix this type since `color` should be valid here. TS can't resolve the correct type.
|
|
57
65
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
58
66
|
// @ts-ignore
|
|
@@ -64,7 +72,12 @@ const MenuGroup = <I extends TypeMenuItemProps = TypeMenuItemProps>({
|
|
|
64
72
|
{title}
|
|
65
73
|
</StyledTitle>
|
|
66
74
|
)}
|
|
67
|
-
<StyledMenuGroupUl
|
|
75
|
+
<StyledMenuGroupUl
|
|
76
|
+
role="group"
|
|
77
|
+
aria-labelledby={title ? titleId : undefined}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</StyledMenuGroupUl>
|
|
68
81
|
</StyledMenuGroup>
|
|
69
82
|
);
|
|
70
83
|
};
|
|
@@ -207,6 +207,25 @@ describe("MenuGroup", () => {
|
|
|
207
207
|
).toBeInTheDocument();
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
+
it("does not place role=group on an <li> (axe aria-allowed-role)", () => {
|
|
211
|
+
render(
|
|
212
|
+
<ActionMenu
|
|
213
|
+
defaultIsOpen
|
|
214
|
+
menuToggleElement={<MenuToggleButton>Test Menu</MenuToggleButton>}
|
|
215
|
+
>
|
|
216
|
+
<MenuContent>
|
|
217
|
+
<MenuGroup id="group">
|
|
218
|
+
<MenuItem id="item-1">Item 1</MenuItem>
|
|
219
|
+
</MenuGroup>
|
|
220
|
+
</MenuContent>
|
|
221
|
+
</ActionMenu>
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// role="group" must live on the inner <ul>, not the outer <li>.
|
|
225
|
+
const group = screen.getByRole("group");
|
|
226
|
+
expect(group.tagName).toBe("UL");
|
|
227
|
+
});
|
|
228
|
+
|
|
210
229
|
it("when no title is provided, it should not render a title", () => {
|
|
211
230
|
render(
|
|
212
231
|
<SingleSelectMenu
|
|
@@ -39,6 +39,26 @@ const MenuItem = ({
|
|
|
39
39
|
|
|
40
40
|
const highlighted = highlightedIndex === item?.index;
|
|
41
41
|
|
|
42
|
+
const downshiftItemProps =
|
|
43
|
+
getItemProps?.({
|
|
44
|
+
...(isListbox ? {} : { role: "menuitem" }),
|
|
45
|
+
item,
|
|
46
|
+
index: item.index,
|
|
47
|
+
// The disabled prop no longer supported in downshift. Use isItemDisabled instead.
|
|
48
|
+
// disabled,
|
|
49
|
+
onClick,
|
|
50
|
+
onKeyDown,
|
|
51
|
+
ref: innerRef,
|
|
52
|
+
}) ?? {};
|
|
53
|
+
|
|
54
|
+
// downshift's useSelect adds aria-selected to every item, but aria-selected
|
|
55
|
+
// is only valid on role="option" (listbox). For menuitem / menuitemcheckbox /
|
|
56
|
+
// menuitemradio it fails axe's aria-allowed-attr rule — menu items use
|
|
57
|
+
// aria-checked for state, not aria-selected.
|
|
58
|
+
if (!isListbox && "aria-selected" in downshiftItemProps) {
|
|
59
|
+
delete downshiftItemProps["aria-selected"];
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
return hiddenItemsMap[item.id] ? (
|
|
43
63
|
<></>
|
|
44
64
|
) : (
|
|
@@ -47,16 +67,7 @@ const MenuItem = ({
|
|
|
47
67
|
id={id}
|
|
48
68
|
$highlighted={highlighted}
|
|
49
69
|
$active={active}
|
|
50
|
-
{...
|
|
51
|
-
...(isListbox ? {} : { role: "menuitem" }),
|
|
52
|
-
item,
|
|
53
|
-
index: item.index,
|
|
54
|
-
// The disabled prop no longer supported in downshift. Use isItemDisabled instead.
|
|
55
|
-
// disabled,
|
|
56
|
-
onClick,
|
|
57
|
-
onKeyDown,
|
|
58
|
-
ref: innerRef,
|
|
59
|
-
})}
|
|
70
|
+
{...downshiftItemProps}
|
|
60
71
|
// use anchor tag if href is provided and button for menuitem
|
|
61
72
|
as={isListbox ? undefined : href ? "a" : "button"}
|
|
62
73
|
type={href ? undefined : "button"}
|
|
@@ -251,6 +251,25 @@ describe("MenuItem when not isListbox", () => {
|
|
|
251
251
|
expect(menuItem).toContainElement(span);
|
|
252
252
|
});
|
|
253
253
|
|
|
254
|
+
it("does not set aria-selected on role=menuitem (axe aria-allowed-attr)", () => {
|
|
255
|
+
render(
|
|
256
|
+
<ActionMenu
|
|
257
|
+
menuToggleElement={testMenuToggleElement}
|
|
258
|
+
isOpen
|
|
259
|
+
id="no-aria-selected"
|
|
260
|
+
>
|
|
261
|
+
<MenuContent>
|
|
262
|
+
<MenuItem {...menuItemProps} />
|
|
263
|
+
</MenuContent>
|
|
264
|
+
</ActionMenu>
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// downshift's useSelect auto-adds aria-selected; it must be stripped
|
|
268
|
+
// for menuitems because aria-selected is only valid on role=option.
|
|
269
|
+
const menuItem = screen.getByRole("menuitem");
|
|
270
|
+
expect(menuItem).not.toHaveAttribute("aria-selected");
|
|
271
|
+
});
|
|
272
|
+
|
|
254
273
|
it("accepts box & system Props", () => {
|
|
255
274
|
render(
|
|
256
275
|
<ActionMenu
|
|
@@ -25,6 +25,7 @@ export interface TypeMenuSearchInputProps extends TypeInputProps {
|
|
|
25
25
|
export const MenuSearchInput = ({
|
|
26
26
|
getIsItemVisible,
|
|
27
27
|
inputProps,
|
|
28
|
+
onChange,
|
|
28
29
|
...restInputProps
|
|
29
30
|
}: TypeMenuSearchInputProps) => {
|
|
30
31
|
const {
|
|
@@ -63,6 +64,7 @@ export const MenuSearchInput = ({
|
|
|
63
64
|
<Input
|
|
64
65
|
onChange={(e, value) => {
|
|
65
66
|
updateFilteredItems({ inputValue: value });
|
|
67
|
+
onChange?.(e, value);
|
|
66
68
|
}}
|
|
67
69
|
// apply onKeyDown logic for downshift keyboard accessibility support
|
|
68
70
|
onKeyDown={handleKeyDown}
|
|
@@ -153,5 +153,28 @@ describe("MenuSearchInput", () => {
|
|
|
153
153
|
|
|
154
154
|
expect(customFilter).toHaveBeenCalled();
|
|
155
155
|
});
|
|
156
|
+
|
|
157
|
+
it("should call the passed onChange prop and still filter items", async () => {
|
|
158
|
+
const handleChange = jest.fn();
|
|
159
|
+
const { user } = render(
|
|
160
|
+
<Component {...defaultProps({ onChange: handleChange })} />
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
await user.click(screen.getByRole("button", { name: "Open Menu" }));
|
|
164
|
+
|
|
165
|
+
const inputElement = screen.getByRole("searchbox") as HTMLInputElement;
|
|
166
|
+
await act(async () => {
|
|
167
|
+
await user.click(inputElement);
|
|
168
|
+
await user.type(inputElement, "Item 0");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// onChange prop was forwarded
|
|
172
|
+
await waitFor(() => expect(handleChange).toHaveBeenCalled());
|
|
173
|
+
|
|
174
|
+
// updateFilteredItems still ran — only the matching item is visible
|
|
175
|
+
await waitFor(() =>
|
|
176
|
+
expect(screen.queryAllByRole(menuItemRole).length).toBe(1)
|
|
177
|
+
);
|
|
178
|
+
});
|
|
156
179
|
});
|
|
157
180
|
});
|