@dhis2-ui/menu 9.11.0 → 9.11.1-beta.2

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.
Files changed (63) hide show
  1. package/build/cjs/flyout-menu/__tests__/flyout-menu.test.js +1 -15
  2. package/build/cjs/flyout-menu/features/accepts_children/index.js +0 -1
  3. package/build/cjs/flyout-menu/features/position/index.js +4 -4
  4. package/build/cjs/flyout-menu/features/toggles_submenus/index.js +0 -1
  5. package/build/cjs/flyout-menu/{flyout-menu.stories.e2e.js → flyout-menu.e2e.stories.js} +2 -20
  6. package/build/cjs/flyout-menu/flyout-menu.js +5 -23
  7. package/build/cjs/flyout-menu/{flyout-menu.stories.js → flyout-menu.prod.stories.js} +25 -37
  8. package/build/cjs/flyout-menu/index.js +0 -1
  9. package/build/cjs/index.js +0 -5
  10. package/build/cjs/menu/__tests__/menu.test.js +11 -50
  11. package/build/cjs/menu/features/accepts_children/index.js +0 -1
  12. package/build/cjs/menu/helpers.js +2 -10
  13. package/build/cjs/menu/index.js +0 -1
  14. package/build/cjs/menu/menu.e2e.stories.js +14 -0
  15. package/build/cjs/menu/menu.js +12 -20
  16. package/build/cjs/menu/{menu.stories.js → menu.prod.stories.js} +18 -17
  17. package/build/cjs/menu/use-menu.js +12 -20
  18. package/build/cjs/menu-divider/index.js +0 -1
  19. package/build/cjs/menu-divider/menu-divider.js +3 -11
  20. package/build/cjs/menu-divider/{menu-divider.stories.js → menu-divider.prod.stories.js} +11 -15
  21. package/build/cjs/menu-item/__tests__/menu-item.test.js +1 -5
  22. package/build/cjs/menu-item/features/accepts_href/index.js +1 -2
  23. package/build/cjs/menu-item/features/accepts_icon/index.js +0 -1
  24. package/build/cjs/menu-item/features/accepts_label/index.js +1 -2
  25. package/build/cjs/menu-item/features/accepts_suffix/index.js +0 -1
  26. package/build/cjs/menu-item/features/accepts_target/index.js +1 -2
  27. package/build/cjs/menu-item/features/is_clickable/index.js +0 -1
  28. package/build/cjs/menu-item/index.js +0 -1
  29. package/build/cjs/menu-item/{menu-item.stories.e2e.js → menu-item.e2e.stories.js} +2 -20
  30. package/build/cjs/menu-item/menu-item.js +11 -46
  31. package/build/cjs/menu-item/{menu-item.stories.js → menu-item.prod.stories.js} +22 -46
  32. package/build/cjs/menu-item/menu-item.styles.js +2 -5
  33. package/build/cjs/menu-section-header/features/accepts_label/index.js +1 -2
  34. package/build/cjs/menu-section-header/index.js +0 -1
  35. package/build/cjs/menu-section-header/{menu-section-header.stories.e2e.js → menu-section-header.e2e.stories.js} +2 -10
  36. package/build/cjs/menu-section-header/menu-section-header.js +3 -12
  37. package/build/cjs/menu-section-header/{menu-section-header.stories.js → menu-section-header.prod.stories.js} +11 -19
  38. package/build/es/flyout-menu/features/position/index.js +4 -3
  39. package/build/es/flyout-menu/{flyout-menu.stories.e2e.js → flyout-menu.e2e.stories.js} +0 -2
  40. package/build/es/flyout-menu/flyout-menu.js +2 -12
  41. package/build/es/flyout-menu/{flyout-menu.stories.js → flyout-menu.prod.stories.js} +25 -12
  42. package/build/es/menu/__tests__/menu.test.js +10 -8
  43. package/build/es/menu/helpers.js +2 -6
  44. package/build/es/menu/menu.e2e.stories.js +6 -0
  45. package/build/es/menu/menu.js +9 -9
  46. package/build/es/menu/{menu.stories.js → menu.prod.stories.js} +18 -1
  47. package/build/es/menu/use-menu.js +12 -16
  48. package/build/es/menu-divider/menu-divider.js +2 -4
  49. package/build/es/menu-divider/{menu-divider.stories.js → menu-divider.prod.stories.js} +7 -3
  50. package/build/es/menu-item/features/accepts_href/index.js +1 -1
  51. package/build/es/menu-item/features/accepts_label/index.js +1 -1
  52. package/build/es/menu-item/features/accepts_target/index.js +1 -1
  53. package/build/es/menu-item/menu-item.js +8 -31
  54. package/build/es/menu-item/{menu-item.stories.js → menu-item.prod.stories.js} +14 -9
  55. package/build/es/menu-item/menu-item.styles.js +1 -1
  56. package/build/es/menu-section-header/features/accepts_label/index.js +1 -1
  57. package/build/es/menu-section-header/menu-section-header.js +2 -4
  58. package/build/es/menu-section-header/{menu-section-header.stories.js → menu-section-header.prod.stories.js} +7 -3
  59. package/package.json +11 -11
  60. package/build/cjs/menu/menu.stories.e2e.js +0 -11
  61. package/build/es/menu/menu.stories.e2e.js +0 -4
  62. /package/build/es/menu-item/{menu-item.stories.e2e.js → menu-item.e2e.stories.js} +0 -0
  63. /package/build/es/menu-section-header/{menu-section-header.stories.e2e.js → menu-section-header.e2e.stories.js} +0 -0
@@ -2,8 +2,7 @@ import _JSXStyle from "styled-jsx/style";
2
2
  import { colors, elevations, spacers } from '@dhis2/ui-constants';
3
3
  import PropTypes from 'prop-types';
4
4
  import React, { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react';
5
- import { Menu } from '../index.js';
6
-
5
+ import { Menu } from '../menu/index.js';
7
6
  const FlyoutMenu = _ref => {
8
7
  let {
9
8
  children,
@@ -15,20 +14,16 @@ const FlyoutMenu = _ref => {
15
14
  closeMenu
16
15
  } = _ref;
17
16
  const [openedSubMenu, setOpenedSubMenu] = useState(null);
18
-
19
17
  const toggleSubMenu = index => {
20
18
  const toggleValue = index === openedSubMenu ? null : index;
21
19
  setOpenedSubMenu(toggleValue);
22
20
  };
23
-
24
21
  const divRef = useRef(null);
25
22
  useEffect(() => {
26
23
  if (!divRef.current) {
27
24
  return;
28
25
  }
29
-
30
26
  const div = divRef.current;
31
-
32
27
  const handleFocus = event => {
33
28
  if (event.target === div) {
34
29
  if (div !== null && div !== void 0 && div.children && div.children.length > 0) {
@@ -36,14 +31,12 @@ const FlyoutMenu = _ref => {
36
31
  }
37
32
  }
38
33
  };
39
-
40
34
  const handleKeyDown = event => {
41
35
  if (event.key === 'Escape') {
42
36
  event.preventDefault();
43
37
  closeMenu && closeMenu();
44
38
  }
45
39
  };
46
-
47
40
  div.addEventListener('focus', handleFocus);
48
41
  div.addEventListener('keydown', handleKeyDown);
49
42
  return () => {
@@ -64,9 +57,8 @@ const FlyoutMenu = _ref => {
64
57
  }) : child)), /*#__PURE__*/React.createElement(_JSXStyle, {
65
58
  id: "3833750986",
66
59
  dynamic: [colors.white, colors.grey200, elevations.e300, dense ? '128' : '180', maxWidth, maxHeight, spacers.dp4]
67
- }, ["div.__jsx-style-dynamic-selector{background:".concat(colors.white, ";border:1px solid ").concat(colors.grey200, ";border-radius:3px;box-shadow:").concat(elevations.e300, ";display:inline-block;min-width:").concat(dense ? '128' : '180', "px;max-width:").concat(maxWidth, ";max-height:").concat(maxHeight, ";padding:").concat(spacers.dp4, " 0;overflow:auto;}")]));
60
+ }, [`div.__jsx-style-dynamic-selector{background:${colors.white};border:1px solid ${colors.grey200};border-radius:3px;box-shadow:${elevations.e300};display:inline-block;min-width:${dense ? '128' : '180'}px;max-width:${maxWidth};max-height:${maxHeight};padding:${spacers.dp4} 0;overflow:auto;}`]));
68
61
  };
69
-
70
62
  FlyoutMenu.defaultProps = {
71
63
  dataTest: 'dhis2-uicore-menu',
72
64
  maxWidth: '380px',
@@ -76,11 +68,9 @@ FlyoutMenu.propTypes = {
76
68
  /** Typically, but not limited to, `MenuItem` components */
77
69
  children: PropTypes.node,
78
70
  className: PropTypes.string,
79
-
80
71
  /** when Escape key is pressed, this function is called to close the flyout menu */
81
72
  closeMenu: PropTypes.func,
82
73
  dataTest: PropTypes.string,
83
-
84
74
  /** Menu uses smaller dimensions */
85
75
  dense: PropTypes.bool,
86
76
  maxHeight: PropTypes.string,
@@ -1,12 +1,28 @@
1
- function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
-
1
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
+ import { IconChevronDown16 } from '@dhis2/ui-icons';
3
3
  import { Layer } from '@dhis2-ui/layer';
4
4
  import { MenuDivider, MenuItem, MenuSectionHeader } from '@dhis2-ui/menu';
5
5
  import { Popper } from '@dhis2-ui/popper';
6
- import { IconChevronDown16 } from '@dhis2/ui-icons';
7
6
  import React, { useState, useRef } from 'react';
8
7
  import { FlyoutMenu } from './flyout-menu.js';
9
- const description = "\nUse menus to provide access to options and actions where space is limited and displaying all the options would be impractical. For example, providing access to a range of actions for every dashboard item displayed. Containing all those actions in menus keeps the page manageable.\n\nThe menu component is flexible in where it can be used and its contents can be flexible too. However, the most common use case is a menu containing menu items.\n\nMake sure the menu item labels are short and easy to understand. One word is often enough to describe an action or option. Do not use sentences as labels. Some examples of good menu item labels:\n\n- \"Save\"\n- \"Open as map\"\n- \"Export PDF\"\n- \"Duplicate\"\n\nSee more about how to use menus at the [design system](https://github.com/dhis2/design-system/blob/master/molecules/menu.md).\n\n```js\nimport { FlyoutMenu } from 'dhis2/ui'\n```\n";
8
+ const description = `
9
+ Use menus to provide access to options and actions where space is limited and displaying all the options would be impractical. For example, providing access to a range of actions for every dashboard item displayed. Containing all those actions in menus keeps the page manageable.
10
+
11
+ The menu component is flexible in where it can be used and its contents can be flexible too. However, the most common use case is a menu containing menu items.
12
+
13
+ Make sure the menu item labels are short and easy to understand. One word is often enough to describe an action or option. Do not use sentences as labels. Some examples of good menu item labels:
14
+
15
+ - "Save"
16
+ - "Open as map"
17
+ - "Export PDF"
18
+ - "Duplicate"
19
+
20
+ See more about how to use menus at the [design system](https://github.com/dhis2/design-system/blob/master/molecules/menu.md).
21
+
22
+ \`\`\`js
23
+ import { FlyoutMenu } from 'dhis2/ui'
24
+ \`\`\`
25
+ `;
10
26
  export default {
11
27
  title: 'Flyout Menu',
12
28
  component: FlyoutMenu,
@@ -60,9 +76,11 @@ export const MaxWidth = args => /*#__PURE__*/React.createElement(React.Fragment,
60
76
  }), /*#__PURE__*/React.createElement(MenuItem, {
61
77
  label: "Item 2 - with a lot of text and using a default maxWidth value of 380px"
62
78
  })), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement(FlyoutMenu, args, /*#__PURE__*/React.createElement(MenuItem, {
63
- label: "Item 1 - with a lot of text and using a custom maxWidth value of\n ".concat(args.maxWidth)
79
+ label: `Item 1 - with a lot of text and using a custom maxWidth value of
80
+ ${args.maxWidth}`
64
81
  }), /*#__PURE__*/React.createElement(MenuItem, {
65
- label: "Item 2 - with a lot of text and using a custom maxWidth value of\n ".concat(args.maxWidth)
82
+ label: `Item 2 - with a lot of text and using a custom maxWidth value of
83
+ ${args.maxWidth}`
66
84
  })));
67
85
  MaxWidth.args = {
68
86
  maxWidth: '300px'
@@ -156,9 +174,7 @@ WithVariousChildren.parameters = {
156
174
  export const DropDownMenu = args => {
157
175
  const ref = useRef();
158
176
  const [open, setOpen] = useState(false);
159
-
160
177
  const toggle = () => setOpen(!open);
161
-
162
178
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
163
179
  ref: ref,
164
180
  onClick: toggle
@@ -198,15 +214,12 @@ export const WithCustomMenuItem = args => {
198
214
  const WIDTH = 1400;
199
215
  const centerY = (window.screen.height - HEIGHT) / 2;
200
216
  const centerX = (window.screen.width - WIDTH) / 2;
201
-
202
- const onClick = () => window.open(to, 'Popup', ['menubar=no', 'location=no', 'resizable=no', 'scrollbars=no', 'status=no', "width=".concat(WIDTH), "height=".concat(HEIGHT), "top=".concat(centerY), "left=".concat(centerX)].join());
203
-
217
+ const onClick = () => window.open(to, 'Popup', ['menubar=no', 'location=no', 'resizable=no', 'scrollbars=no', 'status=no', `width=${WIDTH}`, `height=${HEIGHT}`, `top=${centerY}`, `left=${centerX}`].join());
204
218
  return /*#__PURE__*/React.createElement(MenuItem, _extends({
205
219
  onClick: onClick,
206
220
  label: children
207
221
  }, rest));
208
222
  };
209
-
210
223
  return /*#__PURE__*/React.createElement(FlyoutMenu, args, /*#__PURE__*/React.createElement(MenuItem, {
211
224
  label: "A normal menu item"
212
225
  }), /*#__PURE__*/React.createElement(PopupWindowMenuItem, {
@@ -72,9 +72,9 @@ describe('Menu Component', () => {
72
72
  const menuItem1 = getByText(/Menu item 1/i);
73
73
  const menuItem2 = getByText(/Menu item 2/i);
74
74
  expect(menu).not.toHaveFocus();
75
- userEvent.tab(); // check if LI parent node has focus or not
75
+ userEvent.tab();
76
+ // check if LI parent node has focus or not
76
77
  // headers and dividers do not receive focus
77
-
78
78
  expect(header.parentNode.parentNode).not.toHaveFocus();
79
79
  expect(divider.parentNode.parentNode).not.toHaveFocus();
80
80
  expect(menuItem2.parentNode.parentNode).not.toHaveFocus();
@@ -100,8 +100,8 @@ describe('Menu Component', () => {
100
100
  const menuItem1 = getByText(/Menu item 1/i);
101
101
  const menuItem2 = getByText(/Menu item 2/i);
102
102
  userEvent.tab();
103
- expect(menuItem1.parentNode.parentNode).toHaveFocus(); // simulate arrowDown press
104
-
103
+ expect(menuItem1.parentNode.parentNode).toHaveFocus();
104
+ // simulate arrowDown press
105
105
  userEvent.keyboard('{ArrowDown}');
106
106
  expect(menuItem1.parentNode.parentNode).not.toHaveFocus();
107
107
  expect(menuItem2.parentNode.parentNode).toHaveFocus();
@@ -129,8 +129,9 @@ describe('Menu Component', () => {
129
129
  const menuItem1 = getByText(/Menu item 1/i);
130
130
  const menuItem2 = getByText(/Menu item 2/i);
131
131
  userEvent.tab();
132
- expect(menuItem1.parentNode.parentNode).toHaveFocus(); // simulate arrowUp press
132
+ expect(menuItem1.parentNode.parentNode).toHaveFocus();
133
133
 
134
+ // simulate arrowUp press
134
135
  userEvent.keyboard('{ArrowUp}');
135
136
  expect(menuItem1.parentNode.parentNode).not.toHaveFocus();
136
137
  expect(menuItem2.parentNode.parentNode).toHaveFocus();
@@ -178,8 +179,9 @@ describe('Menu Component', () => {
178
179
  }, "Span 2"))));
179
180
  const nonListMenuItem = getByText(/span 1/i);
180
181
  const listMenuItem = getByText(/link 2/i);
181
- const plainListItem = getByText(/span 2/i); // all children must be list items
182
+ const plainListItem = getByText(/span 2/i);
182
183
 
184
+ // all children must be list items
183
185
  expect(nonListMenuItem.parentElement.nodeName).toBe('LI');
184
186
  userEvent.tab();
185
187
  expect(nonListMenuItem.parentElement).toHaveFocus();
@@ -190,8 +192,8 @@ describe('Menu Component', () => {
190
192
  userEvent.keyboard('{ArrowDown}');
191
193
  expect(listMenuItem.parentElement).toHaveFocus();
192
194
  userEvent.keyboard('{ArrowDown}');
193
- expect(nonListMenuItem.parentElement).toHaveFocus(); // non menu items do not receive focus
194
-
195
+ expect(nonListMenuItem.parentElement).toHaveFocus();
196
+ // non menu items do not receive focus
195
197
  expect(plainListItem.parentElement).not.toHaveFocus();
196
198
  });
197
199
  it('does not hijack input change value if space entered [bug]', () => {
@@ -1,26 +1,23 @@
1
1
  const isMenuItem = role => {
2
2
  return ['menuitem', 'menuitemcheckbox', 'menuitemradio'].includes(role);
3
3
  };
4
-
5
4
  const isValidMenuItemNode = node => {
6
5
  if (node.nodeName === 'LI' && node.firstElementChild) {
7
6
  return isValidMenuItemNode(node.firstElementChild);
8
7
  }
8
+ const role = node.getAttribute('role');
9
9
 
10
- const role = node.getAttribute('role'); // for h1 - h6 headings since their heading role is not explicitly set
10
+ // for h1 - h6 headings since their heading role is not explicitly set
11
11
  // style elements do not have roles
12
-
13
12
  if (node.nodeName.startsWith('H') || node.nodeName === 'STYLE') {
14
13
  return false;
15
14
  }
16
-
17
15
  if (role) {
18
16
  return isMenuItem(role);
19
17
  } else {
20
18
  console.warn('Missing: role attribute on the menu child');
21
19
  }
22
20
  };
23
-
24
21
  export const getFocusableItemsIndices = elements => {
25
22
  const focusableIndices = [];
26
23
  elements.forEach((node, index) => {
@@ -32,6 +29,5 @@ export const getFocusableItemsIndices = elements => {
32
29
  };
33
30
  export const hasMenuItemRole = component => {
34
31
  var _component$props;
35
-
36
32
  return isMenuItem(component === null || component === void 0 ? void 0 : (_component$props = component.props) === null || _component$props === void 0 ? void 0 : _component$props['role']);
37
33
  };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { Menu } from './index.js';
3
+ export default {
4
+ title: 'Menu'
5
+ };
6
+ export const WithChildren = () => /*#__PURE__*/React.createElement(Menu, null, "I am a child");
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
3
3
  import React, { Children, cloneElement, isValidElement, useMemo } from 'react';
4
4
  import { hasMenuItemRole } from './helpers.js';
5
5
  import { useMenuNavigation } from './use-menu.js';
6
-
7
6
  const Menu = _ref => {
8
7
  let {
9
8
  children,
@@ -19,18 +18,20 @@ const Menu = _ref => {
19
18
  if (! /*#__PURE__*/isValidElement(child)) {
20
19
  return child;
21
20
  }
22
-
23
21
  const tabIndex = index === focusedIndex ? 0 : -1;
24
- const childProps = { ...child.props
25
- }; // this check is based on the type of child.
22
+ const childProps = {
23
+ ...child.props
24
+ };
25
+
26
+ // this check is based on the type of child.
26
27
  // if it is a native HTML element, like li, a, span, only apply its child props
27
28
  // if it is a functional (React) component, it applies custom props, like dense, hideDivider, etc
28
-
29
29
  if (typeof child.type === 'string') {
30
30
  // if the native HTML element child is not li, then wrap it in an li tag
31
31
  // apply the tabindex prop if a child has the menuitem role to make it focusable
32
32
  if (child.type === 'li') {
33
- return hasMenuItemRole(child.props.children[0]) ? /*#__PURE__*/cloneElement(child, { ...childProps,
33
+ return hasMenuItemRole(child.props.children[0]) ? /*#__PURE__*/cloneElement(child, {
34
+ ...childProps,
34
35
  tabIndex
35
36
  }) : /*#__PURE__*/cloneElement(child, childProps);
36
37
  } else {
@@ -41,7 +42,8 @@ const Menu = _ref => {
41
42
  } else {
42
43
  childProps.dense = typeof child.props.dense === 'boolean' ? child.props.dense : dense;
43
44
  childProps.hideDivider = typeof child.props.hideDivider !== 'boolean' && index === 0 ? true : child.props.hideDivider;
44
- return /*#__PURE__*/cloneElement(child, { ...childProps,
45
+ return /*#__PURE__*/cloneElement(child, {
46
+ ...childProps,
45
47
  tabIndex
46
48
  });
47
49
  }
@@ -56,7 +58,6 @@ const Menu = _ref => {
56
58
  id: "1636612837"
57
59
  }, ["ul.jsx-1636612837{display:block;position:relative;width:100%;margin:0;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}"]));
58
60
  };
59
-
60
61
  Menu.defaultProps = {
61
62
  dataTest: 'dhis2-uicore-menulist'
62
63
  };
@@ -65,7 +66,6 @@ Menu.propTypes = {
65
66
  children: PropTypes.node,
66
67
  className: PropTypes.string,
67
68
  dataTest: PropTypes.string,
68
-
69
69
  /** Applies `dense` property to all child components unless already specified */
70
70
  dense: PropTypes.bool
71
71
  };
@@ -1,7 +1,24 @@
1
1
  import React from 'react';
2
2
  import { MenuItem, MenuSectionHeader } from '../index.js';
3
3
  import { Menu } from './index.js';
4
- const description = "\nUse menus to provide access to options and actions where space is limited and displaying all the options would be impractical. For example, providing access to a range of actions for every dashboard item displayed. Containing all those actions in menus keeps the page manageable.\n\nThe menu component is flexible in where it can be used and its contents can be flexible too. However, the most common use case is a menu containing menu items.\n\nMake sure the menu item labels are short and easy to understand. One word is often enough to describe an action or option. Do not use sentences as labels. Some examples of good menu item labels:\n\n- \"Save\"\n- \"Open as map\"\n- \"Export PDF\"\n- \"Duplicate\"\n\nTypical children are Menu Items, Menu Dividers, and Menu Section Headers.\n\n```js\nimport { Menu } from '@dhis2/ui'\n```\n";
4
+ const description = `
5
+ Use menus to provide access to options and actions where space is limited and displaying all the options would be impractical. For example, providing access to a range of actions for every dashboard item displayed. Containing all those actions in menus keeps the page manageable.
6
+
7
+ The menu component is flexible in where it can be used and its contents can be flexible too. However, the most common use case is a menu containing menu items.
8
+
9
+ Make sure the menu item labels are short and easy to understand. One word is often enough to describe an action or option. Do not use sentences as labels. Some examples of good menu item labels:
10
+
11
+ - "Save"
12
+ - "Open as map"
13
+ - "Export PDF"
14
+ - "Duplicate"
15
+
16
+ Typical children are Menu Items, Menu Dividers, and Menu Section Headers.
17
+
18
+ \`\`\`js
19
+ import { Menu } from '@dhis2/ui'
20
+ \`\`\`
21
+ `;
5
22
  export default {
6
23
  title: 'Menu',
7
24
  component: Menu,
@@ -3,17 +3,19 @@ import { getFocusableItemsIndices } from './helpers.js';
3
3
  export const useMenuNavigation = children => {
4
4
  const menuRef = useRef(null);
5
5
  const [focusableItemsIndices, setFocusableItemsIndices] = useState(null);
6
- const [activeItemIndex, setActiveItemIndex] = useState(-1); // Initializes the indices for focusable items
7
- // focusable items have the role of menuitem || menuitemcheckbox || menuitemradio
6
+ const [activeItemIndex, setActiveItemIndex] = useState(-1);
8
7
 
8
+ // Initializes the indices for focusable items
9
+ // focusable items have the role of menuitem || menuitemcheckbox || menuitemradio
9
10
  useEffect(() => {
10
11
  if (menuRef) {
11
12
  const menuItems = Array.from(menuRef.current.children);
12
13
  const itemsIndices = getFocusableItemsIndices(menuItems);
13
14
  setFocusableItemsIndices(itemsIndices);
14
15
  }
15
- }, [children]); // Focus the active menu child
16
+ }, [children]);
16
17
 
18
+ // Focus the active menu child
17
19
  useEffect(() => {
18
20
  if (menuRef) {
19
21
  if (focusableItemsIndices !== null && focusableItemsIndices !== void 0 && focusableItemsIndices.length && activeItemIndex > -1) {
@@ -21,50 +23,45 @@ export const useMenuNavigation = children => {
21
23
  menuRef.current.children[currentIndex].focus();
22
24
  }
23
25
  }
24
- }, [activeItemIndex, focusableItemsIndices]); // Navigate through focusable children using arrow keys
25
- // Trigger actionable items
26
+ }, [activeItemIndex, focusableItemsIndices]);
26
27
 
28
+ // Navigate through focusable children using arrow keys
29
+ // Trigger actionable items
27
30
  const handleKeyDown = useCallback(event => {
28
31
  const totalFocusablePositions = focusableItemsIndices === null || focusableItemsIndices === void 0 ? void 0 : focusableItemsIndices.length;
29
-
30
32
  if (totalFocusablePositions) {
31
33
  const lastIndex = totalFocusablePositions - 1;
32
-
33
34
  switch (event.key) {
34
35
  case 'ArrowUp':
35
36
  event.preventDefault();
36
37
  setActiveItemIndex(activeItemIndex > 0 ? activeItemIndex - 1 : lastIndex);
37
38
  break;
38
-
39
39
  case 'ArrowDown':
40
40
  event.preventDefault();
41
41
  setActiveItemIndex(activeItemIndex >= lastIndex ? 0 : activeItemIndex + 1);
42
42
  break;
43
-
44
43
  case 'Enter':
45
44
  case ' ':
46
45
  if (event.target.nodeName === 'LI') {
47
46
  var _event$target$childre, _event$target$childre2;
48
-
49
47
  event.preventDefault();
50
48
  (_event$target$childre = event.target.children) === null || _event$target$childre === void 0 ? void 0 : (_event$target$childre2 = _event$target$childre[0]) === null || _event$target$childre2 === void 0 ? void 0 : _event$target$childre2.click();
51
49
  }
52
-
53
50
  break;
54
-
55
51
  default:
56
52
  break;
57
53
  }
58
54
  }
59
- }, [activeItemIndex, focusableItemsIndices === null || focusableItemsIndices === void 0 ? void 0 : focusableItemsIndices.length]); // Event listeners for menu focus and key handling
55
+ }, [activeItemIndex, focusableItemsIndices === null || focusableItemsIndices === void 0 ? void 0 : focusableItemsIndices.length]);
60
56
 
57
+ // Event listeners for menu focus and key handling
61
58
  useEffect(() => {
62
59
  if (!menuRef) {
63
60
  return;
64
61
  }
62
+ const menu = menuRef.current;
65
63
 
66
- const menu = menuRef.current; // Focus the first menu item when the menu receives focus
67
-
64
+ // Focus the first menu item when the menu receives focus
68
65
  const handleFocus = event => {
69
66
  if (event.target === menuRef.current) {
70
67
  const firstItemIndex = focusableItemsIndices === null || focusableItemsIndices === void 0 ? void 0 : focusableItemsIndices[0];
@@ -72,7 +69,6 @@ export const useMenuNavigation = children => {
72
69
  setActiveItemIndex(0);
73
70
  }
74
71
  };
75
-
76
72
  menu.addEventListener('focus', handleFocus);
77
73
  menu.addEventListener('keydown', handleKeyDown);
78
74
  return () => {
@@ -1,9 +1,8 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
- import { Divider } from '@dhis2-ui/divider';
3
2
  import { colors } from '@dhis2/ui-constants';
3
+ import { Divider } from '@dhis2-ui/divider';
4
4
  import PropTypes from 'prop-types';
5
5
  import React from 'react';
6
-
7
6
  const MenuDivider = _ref => {
8
7
  let {
9
8
  className,
@@ -18,9 +17,8 @@ const MenuDivider = _ref => {
18
17
  }), /*#__PURE__*/React.createElement(_JSXStyle, {
19
18
  id: "591815244",
20
19
  dynamic: [colors.white]
21
- }, ["li.__jsx-style-dynamic-selector{list-style:none;background-color:".concat(colors.white, ";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:0;line-height:0;}")]));
20
+ }, [`li.__jsx-style-dynamic-selector{list-style:none;background-color:${colors.white};-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding:0;line-height:0;}`]));
22
21
  };
23
-
24
22
  MenuDivider.defaultProps = {
25
23
  dataTest: 'dhis2-uicore-menudivider'
26
24
  };
@@ -1,7 +1,13 @@
1
1
  import React from 'react';
2
2
  import { Menu, MenuItem } from '../index.js';
3
3
  import { MenuDivider } from './menu-divider.js';
4
- const description = "\nItems in a menu can be split into separate sections by using dividers. Group relevant menu items together to help the user understand the options quickly. A divider can be used alone. If using a MenuSectionHeader, a divider will be automatically included. Try not to group single menu items together. An exception to this is a critical destructive menu item, like 'Delete', which can be separated from other menu items.\n\n```js\nimport { MenuDivider } from '@dhis2/ui'\n```\n";
4
+ const description = `
5
+ Items in a menu can be split into separate sections by using dividers. Group relevant menu items together to help the user understand the options quickly. A divider can be used alone. If using a MenuSectionHeader, a divider will be automatically included. Try not to group single menu items together. An exception to this is a critical destructive menu item, like 'Delete', which can be separated from other menu items.
6
+
7
+ \`\`\`js
8
+ import { MenuDivider } from '@dhis2/ui'
9
+ \`\`\`
10
+ `;
5
11
  export default {
6
12
  title: 'Menu Divider',
7
13
  component: MenuDivider,
@@ -13,13 +19,11 @@ export default {
13
19
  }
14
20
  }
15
21
  };
16
-
17
22
  const Template = args => /*#__PURE__*/React.createElement(Menu, null, /*#__PURE__*/React.createElement(MenuItem, {
18
23
  label: "Item above divider"
19
24
  }), /*#__PURE__*/React.createElement(MenuDivider, args), /*#__PURE__*/React.createElement(MenuItem, {
20
25
  label: "Item below divider"
21
26
  }));
22
-
23
27
  export const Default = Template.bind({});
24
28
  export const Dense = Template.bind({});
25
29
  Dense.args = {
@@ -3,5 +3,5 @@ Given('a MenuItem with href is rendered', () => {
3
3
  cy.visitStory('MenuItem', 'With Href');
4
4
  });
5
5
  Then('a link is rendered with the href', () => {
6
- cy.get('a').should('have.attr', 'href').and('include', 'url.test');
6
+ cy.get('#storybook-root a').should('have.attr', 'href').and('include', 'url.test');
7
7
  });
@@ -4,5 +4,5 @@ Given('a MenuItem supplied with a label is rendered', () => {
4
4
  cy.get('[data-test="dhis2-uicore-menuitem"]').should('be.visible');
5
5
  });
6
6
  Then('the label is visible', () => {
7
- cy.contains('label').should('be.visible');
7
+ cy.get(':contains("label")').should('be.visible');
8
8
  });
@@ -3,5 +3,5 @@ Given('a MenuItem with target is rendered', () => {
3
3
  cy.visitStory('MenuItem', 'With Target');
4
4
  });
5
5
  Then('a link is rendered with the target', () => {
6
- cy.get('a').should('have.attr', 'target').and('include', '_blank');
6
+ cy.get('#storybook-root a').should('have.attr', 'target').and('include', '_blank');
7
7
  });
@@ -1,15 +1,13 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
+ import { IconChevronRight24 } from '@dhis2/ui-icons';
2
3
  import { Popper } from '@dhis2-ui/popper';
3
4
  import { Portal } from '@dhis2-ui/portal';
4
- import { IconChevronRight24 } from '@dhis2/ui-icons';
5
5
  import cx from 'classnames';
6
6
  import PropTypes from 'prop-types';
7
7
  import React, { useEffect, useRef, useState } from 'react';
8
- import { FlyoutMenu } from '../index.js';
8
+ import { FlyoutMenu } from '../flyout-menu/index.js';
9
9
  import styles from './menu-item.styles.js';
10
-
11
10
  const isModifiedEvent = evt => evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey;
12
-
13
11
  const createOnClickHandler = _ref => {
14
12
  let {
15
13
  onClick,
@@ -21,7 +19,6 @@ const createOnClickHandler = _ref => {
21
19
  if (isLink && isModifiedEvent(evt) || !(onClick || toggleSubMenu)) {
22
20
  return;
23
21
  }
24
-
25
22
  evt.preventDefault();
26
23
  evt.stopPropagation();
27
24
  onClick && onClick({
@@ -30,7 +27,6 @@ const createOnClickHandler = _ref => {
30
27
  toggleSubMenu && toggleSubMenu();
31
28
  };
32
29
  };
33
-
34
30
  const MenuItem = _ref2 => {
35
31
  let {
36
32
  href,
@@ -64,26 +60,19 @@ const MenuItem = _ref2 => {
64
60
  if (!menuItemRef.current) {
65
61
  return;
66
62
  }
67
-
68
63
  const menuItem = menuItemRef.current;
69
-
70
64
  const handleKeyDown = event => {
71
65
  var _openSubMenus, _openSubMenus2;
72
-
73
66
  const firstChild = event.target.children[0];
74
67
  const hasSubMenu = firstChild === null || firstChild === void 0 ? void 0 : firstChild.getAttribute('aria-haspopup');
75
-
76
68
  switch (event.key) {
77
69
  // for submenus
78
70
  case 'ArrowRight':
79
71
  event.preventDefault();
80
-
81
72
  if (hasSubMenu) {
82
73
  firstChild.click();
83
74
  }
84
-
85
75
  break;
86
-
87
76
  case 'ArrowLeft':
88
77
  case 'Escape':
89
78
  // close flyout menu
@@ -93,7 +82,6 @@ const MenuItem = _ref2 => {
93
82
  break;
94
83
  }
95
84
  };
96
-
97
85
  menuItem.addEventListener('keydown', handleKeyDown);
98
86
  return () => {
99
87
  menuItem.removeEventListener('keydown', handleKeyDown);
@@ -105,7 +93,7 @@ const MenuItem = _ref2 => {
105
93
  role: "presentation",
106
94
  tabIndex: tabIndex,
107
95
  "data-submenu-open": children && showSubMenu,
108
- className: "jsx-".concat(styles.__hash) + " " + (cx(className, {
96
+ className: `jsx-${styles.__hash}` + " " + (cx(className, {
109
97
  destructive,
110
98
  disabled,
111
99
  dense,
@@ -127,15 +115,15 @@ const MenuItem = _ref2 => {
127
115
  "aria-haspopup": children && 'menu',
128
116
  "aria-expanded": showSubMenu,
129
117
  "aria-label": label,
130
- className: "jsx-".concat(styles.__hash)
118
+ className: `jsx-${styles.__hash}`
131
119
  }, icon && /*#__PURE__*/React.createElement("span", {
132
- className: "jsx-".concat(styles.__hash) + " " + "icon"
120
+ className: `jsx-${styles.__hash}` + " " + "icon"
133
121
  }, icon), /*#__PURE__*/React.createElement("span", {
134
- className: "jsx-".concat(styles.__hash) + " " + "label"
122
+ className: `jsx-${styles.__hash}` + " " + "label"
135
123
  }, label), suffix && /*#__PURE__*/React.createElement("span", {
136
- className: "jsx-".concat(styles.__hash) + " " + "suffix"
124
+ className: `jsx-${styles.__hash}` + " " + "suffix"
137
125
  }, suffix), (chevron || children) && /*#__PURE__*/React.createElement("span", {
138
- className: "jsx-".concat(styles.__hash) + " " + "chevron"
126
+ className: `jsx-${styles.__hash}` + " " + "chevron"
139
127
  }, /*#__PURE__*/React.createElement(IconChevronRight24, null))), /*#__PURE__*/React.createElement(_JSXStyle, {
140
128
  id: styles.__hash
141
129
  }, styles)), children && showSubMenu && /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(Popper, {
@@ -145,7 +133,6 @@ const MenuItem = _ref2 => {
145
133
  dense: dense
146
134
  }, children))));
147
135
  };
148
-
149
136
  MenuItem.defaultProps = {
150
137
  dataTest: 'dhis2-uicore-menuitem'
151
138
  };
@@ -154,7 +141,6 @@ MenuItem.propTypes = {
154
141
  checkbox: PropTypes.bool,
155
142
  checked: PropTypes.bool,
156
143
  chevron: PropTypes.bool,
157
-
158
144
  /**
159
145
  * Nested menu items can become submenus.
160
146
  * See `showSubMenu` and `toggleSubMenu` props, and 'Children' demo
@@ -165,32 +151,23 @@ MenuItem.propTypes = {
165
151
  dense: PropTypes.bool,
166
152
  destructive: PropTypes.bool,
167
153
  disabled: PropTypes.bool,
168
-
169
154
  /** For using menu item as a link */
170
155
  href: PropTypes.string,
171
-
172
156
  /** An icon for the left side of the menu item */
173
157
  icon: PropTypes.node,
174
-
175
158
  /** Text in the menu item */
176
159
  label: PropTypes.node,
177
-
178
160
  /** When true, nested menu items are shown in a Popper */
179
161
  showSubMenu: PropTypes.bool,
180
-
181
162
  /** A supporting element shown at the end of the menu item */
182
163
  suffix: PropTypes.node,
183
164
  tabIndex: PropTypes.number,
184
-
185
165
  /** For using menu item as a link */
186
166
  target: PropTypes.string,
187
-
188
167
  /** On click, this function is called (without args) */
189
168
  toggleSubMenu: PropTypes.func,
190
-
191
169
  /** Value associated with item. Passed as an argument to onClick handler. */
192
170
  value: PropTypes.string,
193
-
194
171
  /** Click handler called with signature `({ value: string }, event)` */
195
172
  onClick: PropTypes.func
196
173
  };