@dhis2-ui/menu 10.0.4 → 10.1.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.
|
@@ -67,4 +67,39 @@ describe('Menu Component', () => {
|
|
|
67
67
|
label: 'Test submenu child'
|
|
68
68
|
}).exists()).toBe(false);
|
|
69
69
|
});
|
|
70
|
+
describe('aria-label attribute', () => {
|
|
71
|
+
it('produces a log if the label prop is not a string', () => {
|
|
72
|
+
// Prep to test logs
|
|
73
|
+
const originalConsoleDebug = console.debug;
|
|
74
|
+
const consoleDebugMock = jest.fn();
|
|
75
|
+
console.debug = consoleDebugMock;
|
|
76
|
+
const menuItemDataTest = 'data-test-menu-item';
|
|
77
|
+
const wrapper = (0, _enzyme.mount)(/*#__PURE__*/_react.default.createElement(_menuItem.MenuItem, {
|
|
78
|
+
dataTest: menuItemDataTest,
|
|
79
|
+
label: /*#__PURE__*/_react.default.createElement("span", null, 'Node label')
|
|
80
|
+
}));
|
|
81
|
+
const menuItem = wrapper.find({
|
|
82
|
+
'data-test': menuItemDataTest
|
|
83
|
+
});
|
|
84
|
+
expect(menuItem.childAt(0).prop('role')).toBe('menuitem');
|
|
85
|
+
expect(menuItem.childAt(0).prop('aria-label')).toBe(undefined);
|
|
86
|
+
expect(consoleDebugMock).toHaveBeenCalledWith('The label prop on MenuItem is not a string; a value for the ariaLabel prop should be provided');
|
|
87
|
+
|
|
88
|
+
// Teardown
|
|
89
|
+
console.debug = originalConsoleDebug;
|
|
90
|
+
});
|
|
91
|
+
it('uses the ariaLabel prop for aria-label if defined', () => {
|
|
92
|
+
const menuItemDataTest = 'data-test-menu-item';
|
|
93
|
+
const wrapper = (0, _enzyme.mount)(/*#__PURE__*/_react.default.createElement(_menuItem.MenuItem, {
|
|
94
|
+
dataTest: menuItemDataTest,
|
|
95
|
+
label: /*#__PURE__*/_react.default.createElement("span", null, 'Node label'),
|
|
96
|
+
ariaLabel: "Aria label"
|
|
97
|
+
}));
|
|
98
|
+
const menuItem = wrapper.find({
|
|
99
|
+
'data-test': menuItemDataTest
|
|
100
|
+
});
|
|
101
|
+
expect(menuItem.childAt(0).prop('role')).toBe('menuitem');
|
|
102
|
+
expect(menuItem.childAt(0).prop('aria-label')).toBe('Aria label');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
70
105
|
});
|
|
@@ -52,6 +52,7 @@ const MenuItem = _ref2 => {
|
|
|
52
52
|
chevron,
|
|
53
53
|
value,
|
|
54
54
|
label,
|
|
55
|
+
ariaLabel,
|
|
55
56
|
showSubMenu,
|
|
56
57
|
toggleSubMenu,
|
|
57
58
|
suffix,
|
|
@@ -96,6 +97,12 @@ const MenuItem = _ref2 => {
|
|
|
96
97
|
menuItem.removeEventListener('keydown', handleKeyDown);
|
|
97
98
|
};
|
|
98
99
|
}, [openSubMenus]);
|
|
100
|
+
const resolvedAriaLabel = (0, _react.useMemo)(() => {
|
|
101
|
+
if (typeof label !== 'string' && ariaLabel === undefined) {
|
|
102
|
+
console.debug('The label prop on MenuItem is not a string; a value for the ariaLabel prop should be provided');
|
|
103
|
+
}
|
|
104
|
+
return ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : typeof label === 'string' ? label : undefined;
|
|
105
|
+
}, [ariaLabel, label]);
|
|
99
106
|
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("li", {
|
|
100
107
|
ref: menuItemRef,
|
|
101
108
|
"data-test": dataTest,
|
|
@@ -123,7 +130,7 @@ const MenuItem = _ref2 => {
|
|
|
123
130
|
"aria-disabled": disabled,
|
|
124
131
|
"aria-haspopup": children && 'menu',
|
|
125
132
|
"aria-expanded": showSubMenu,
|
|
126
|
-
"aria-label":
|
|
133
|
+
"aria-label": resolvedAriaLabel,
|
|
127
134
|
className: `jsx-${_menuItemStyles.default.__hash}`
|
|
128
135
|
}, icon && /*#__PURE__*/_react.default.createElement("span", {
|
|
129
136
|
className: `jsx-${_menuItemStyles.default.__hash}` + " " + "icon"
|
|
@@ -145,6 +152,12 @@ const MenuItem = _ref2 => {
|
|
|
145
152
|
exports.MenuItem = MenuItem;
|
|
146
153
|
MenuItem.propTypes = {
|
|
147
154
|
active: _propTypes.default.bool,
|
|
155
|
+
/**
|
|
156
|
+
* By default, the label prop is used for the aria-label attribute on the
|
|
157
|
+
* underlying HTML element. If this prop is defined, it will be used as the
|
|
158
|
+
* aria-label instead
|
|
159
|
+
*/
|
|
160
|
+
ariaLabel: _propTypes.default.string,
|
|
148
161
|
checkbox: _propTypes.default.bool,
|
|
149
162
|
checked: _propTypes.default.bool,
|
|
150
163
|
chevron: _propTypes.default.bool,
|
|
@@ -162,7 +175,7 @@ MenuItem.propTypes = {
|
|
|
162
175
|
href: _propTypes.default.string,
|
|
163
176
|
/** An icon for the left side of the menu item */
|
|
164
177
|
icon: _propTypes.default.node,
|
|
165
|
-
/** Text in the menu item */
|
|
178
|
+
/** Text in the menu item. If it's a string, will be used as aria-label */
|
|
166
179
|
label: _propTypes.default.node,
|
|
167
180
|
/** When true, nested menu items are shown in a Popper */
|
|
168
181
|
showSubMenu: _propTypes.default.bool,
|
|
@@ -64,4 +64,39 @@ describe('Menu Component', () => {
|
|
|
64
64
|
label: 'Test submenu child'
|
|
65
65
|
}).exists()).toBe(false);
|
|
66
66
|
});
|
|
67
|
+
describe('aria-label attribute', () => {
|
|
68
|
+
it('produces a log if the label prop is not a string', () => {
|
|
69
|
+
// Prep to test logs
|
|
70
|
+
const originalConsoleDebug = console.debug;
|
|
71
|
+
const consoleDebugMock = jest.fn();
|
|
72
|
+
console.debug = consoleDebugMock;
|
|
73
|
+
const menuItemDataTest = 'data-test-menu-item';
|
|
74
|
+
const wrapper = mount(/*#__PURE__*/React.createElement(MenuItem, {
|
|
75
|
+
dataTest: menuItemDataTest,
|
|
76
|
+
label: /*#__PURE__*/React.createElement("span", null, 'Node label')
|
|
77
|
+
}));
|
|
78
|
+
const menuItem = wrapper.find({
|
|
79
|
+
'data-test': menuItemDataTest
|
|
80
|
+
});
|
|
81
|
+
expect(menuItem.childAt(0).prop('role')).toBe('menuitem');
|
|
82
|
+
expect(menuItem.childAt(0).prop('aria-label')).toBe(undefined);
|
|
83
|
+
expect(consoleDebugMock).toHaveBeenCalledWith('The label prop on MenuItem is not a string; a value for the ariaLabel prop should be provided');
|
|
84
|
+
|
|
85
|
+
// Teardown
|
|
86
|
+
console.debug = originalConsoleDebug;
|
|
87
|
+
});
|
|
88
|
+
it('uses the ariaLabel prop for aria-label if defined', () => {
|
|
89
|
+
const menuItemDataTest = 'data-test-menu-item';
|
|
90
|
+
const wrapper = mount(/*#__PURE__*/React.createElement(MenuItem, {
|
|
91
|
+
dataTest: menuItemDataTest,
|
|
92
|
+
label: /*#__PURE__*/React.createElement("span", null, 'Node label'),
|
|
93
|
+
ariaLabel: "Aria label"
|
|
94
|
+
}));
|
|
95
|
+
const menuItem = wrapper.find({
|
|
96
|
+
'data-test': menuItemDataTest
|
|
97
|
+
});
|
|
98
|
+
expect(menuItem.childAt(0).prop('role')).toBe('menuitem');
|
|
99
|
+
expect(menuItem.childAt(0).prop('aria-label')).toBe('Aria label');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
67
102
|
});
|
|
@@ -4,7 +4,7 @@ import { Popper } from '@dhis2-ui/popper';
|
|
|
4
4
|
import { Portal } from '@dhis2-ui/portal';
|
|
5
5
|
import cx from 'classnames';
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
|
-
import React, { useEffect, useRef, useState } from 'react';
|
|
7
|
+
import React, { useEffect, useRef, useState, useMemo } from 'react';
|
|
8
8
|
import { FlyoutMenu } from '../flyout-menu/index.js';
|
|
9
9
|
import styles from './menu-item.styles.js';
|
|
10
10
|
const isModifiedEvent = evt => evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey;
|
|
@@ -43,6 +43,7 @@ const MenuItem = _ref2 => {
|
|
|
43
43
|
chevron,
|
|
44
44
|
value,
|
|
45
45
|
label,
|
|
46
|
+
ariaLabel,
|
|
46
47
|
showSubMenu,
|
|
47
48
|
toggleSubMenu,
|
|
48
49
|
suffix,
|
|
@@ -87,6 +88,12 @@ const MenuItem = _ref2 => {
|
|
|
87
88
|
menuItem.removeEventListener('keydown', handleKeyDown);
|
|
88
89
|
};
|
|
89
90
|
}, [openSubMenus]);
|
|
91
|
+
const resolvedAriaLabel = useMemo(() => {
|
|
92
|
+
if (typeof label !== 'string' && ariaLabel === undefined) {
|
|
93
|
+
console.debug('The label prop on MenuItem is not a string; a value for the ariaLabel prop should be provided');
|
|
94
|
+
}
|
|
95
|
+
return ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : typeof label === 'string' ? label : undefined;
|
|
96
|
+
}, [ariaLabel, label]);
|
|
90
97
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("li", {
|
|
91
98
|
ref: menuItemRef,
|
|
92
99
|
"data-test": dataTest,
|
|
@@ -114,7 +121,7 @@ const MenuItem = _ref2 => {
|
|
|
114
121
|
"aria-disabled": disabled,
|
|
115
122
|
"aria-haspopup": children && 'menu',
|
|
116
123
|
"aria-expanded": showSubMenu,
|
|
117
|
-
"aria-label":
|
|
124
|
+
"aria-label": resolvedAriaLabel,
|
|
118
125
|
className: `jsx-${styles.__hash}`
|
|
119
126
|
}, icon && /*#__PURE__*/React.createElement("span", {
|
|
120
127
|
className: `jsx-${styles.__hash}` + " " + "icon"
|
|
@@ -135,6 +142,12 @@ const MenuItem = _ref2 => {
|
|
|
135
142
|
};
|
|
136
143
|
MenuItem.propTypes = {
|
|
137
144
|
active: PropTypes.bool,
|
|
145
|
+
/**
|
|
146
|
+
* By default, the label prop is used for the aria-label attribute on the
|
|
147
|
+
* underlying HTML element. If this prop is defined, it will be used as the
|
|
148
|
+
* aria-label instead
|
|
149
|
+
*/
|
|
150
|
+
ariaLabel: PropTypes.string,
|
|
138
151
|
checkbox: PropTypes.bool,
|
|
139
152
|
checked: PropTypes.bool,
|
|
140
153
|
chevron: PropTypes.bool,
|
|
@@ -152,7 +165,7 @@ MenuItem.propTypes = {
|
|
|
152
165
|
href: PropTypes.string,
|
|
153
166
|
/** An icon for the left side of the menu item */
|
|
154
167
|
icon: PropTypes.node,
|
|
155
|
-
/** Text in the menu item */
|
|
168
|
+
/** Text in the menu item. If it's a string, will be used as aria-label */
|
|
156
169
|
label: PropTypes.node,
|
|
157
170
|
/** When true, nested menu items are shown in a Popper */
|
|
158
171
|
showSubMenu: PropTypes.bool,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dhis2-ui/menu",
|
|
3
|
-
"version": "10.0
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"description": "UI Menu",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@dhis2/prop-types": "^3.1.2",
|
|
36
|
-
"@dhis2-ui/card": "10.0
|
|
37
|
-
"@dhis2-ui/divider": "10.0
|
|
38
|
-
"@dhis2-ui/layer": "10.0
|
|
39
|
-
"@dhis2-ui/popper": "10.0
|
|
40
|
-
"@dhis2-ui/portal": "10.0
|
|
41
|
-
"@dhis2/ui-constants": "10.0
|
|
42
|
-
"@dhis2/ui-icons": "10.0
|
|
36
|
+
"@dhis2-ui/card": "10.1.0",
|
|
37
|
+
"@dhis2-ui/divider": "10.1.0",
|
|
38
|
+
"@dhis2-ui/layer": "10.1.0",
|
|
39
|
+
"@dhis2-ui/popper": "10.1.0",
|
|
40
|
+
"@dhis2-ui/portal": "10.1.0",
|
|
41
|
+
"@dhis2/ui-constants": "10.1.0",
|
|
42
|
+
"@dhis2/ui-icons": "10.1.0",
|
|
43
43
|
"classnames": "^2.3.1",
|
|
44
44
|
"prop-types": "^15.7.2"
|
|
45
45
|
},
|