@dhis2-ui/menu 10.16.2 → 10.16.3-alpha.1

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 (49) hide show
  1. package/package.json +10 -9
  2. package/src/flyout-menu/__tests__/flyout-menu.test.js +48 -0
  3. package/src/flyout-menu/features/accepts_children/index.js +10 -0
  4. package/src/flyout-menu/features/accepts_children.feature +5 -0
  5. package/src/flyout-menu/features/position/index.js +80 -0
  6. package/src/flyout-menu/features/position.feature +19 -0
  7. package/src/flyout-menu/features/toggles_submenus/index.js +26 -0
  8. package/src/flyout-menu/features/toggles_submenus.feature +9 -0
  9. package/src/flyout-menu/flyout-menu.e2e.stories.js +55 -0
  10. package/src/flyout-menu/flyout-menu.js +108 -0
  11. package/src/flyout-menu/flyout-menu.prod.stories.js +229 -0
  12. package/src/flyout-menu/index.js +1 -0
  13. package/src/index.js +6 -0
  14. package/src/menu/__tests__/menu.test.js +213 -0
  15. package/src/menu/features/accepts_children/index.js +10 -0
  16. package/src/menu/features/accepts_children.feature +5 -0
  17. package/src/menu/helpers.js +36 -0
  18. package/src/menu/index.js +1 -0
  19. package/src/menu/menu.e2e.stories.js +5 -0
  20. package/src/menu/menu.js +96 -0
  21. package/src/menu/menu.prod.stories.js +90 -0
  22. package/src/menu/use-menu.js +100 -0
  23. package/src/menu-divider/index.js +1 -0
  24. package/src/menu-divider/menu-divider.js +32 -0
  25. package/src/menu-divider/menu-divider.prod.stories.js +30 -0
  26. package/src/menu-item/__tests__/menu-item.test.js +118 -0
  27. package/src/menu-item/features/accepts_href/index.js +11 -0
  28. package/src/menu-item/features/accepts_href.feature +5 -0
  29. package/src/menu-item/features/accepts_icon/index.js +10 -0
  30. package/src/menu-item/features/accepts_icon.feature +5 -0
  31. package/src/menu-item/features/accepts_label/index.js +10 -0
  32. package/src/menu-item/features/accepts_label.feature +5 -0
  33. package/src/menu-item/features/accepts_suffix/index.js +10 -0
  34. package/src/menu-item/features/accepts_suffix.feature +5 -0
  35. package/src/menu-item/features/accepts_target/index.js +11 -0
  36. package/src/menu-item/features/accepts_target.feature +5 -0
  37. package/src/menu-item/features/is_clickable/index.js +17 -0
  38. package/src/menu-item/features/is_clickable.feature +6 -0
  39. package/src/menu-item/index.js +1 -0
  40. package/src/menu-item/menu-item.e2e.stories.js +29 -0
  41. package/src/menu-item/menu-item.js +204 -0
  42. package/src/menu-item/menu-item.prod.stories.js +197 -0
  43. package/src/menu-item/menu-item.styles.js +133 -0
  44. package/src/menu-section-header/features/accepts_label/index.js +10 -0
  45. package/src/menu-section-header/features/accepts_label.feature +5 -0
  46. package/src/menu-section-header/index.js +1 -0
  47. package/src/menu-section-header/menu-section-header.e2e.stories.js +11 -0
  48. package/src/menu-section-header/menu-section-header.js +52 -0
  49. package/src/menu-section-header/menu-section-header.prod.stories.js +52 -0
@@ -0,0 +1,118 @@
1
+ import { mount } from 'enzyme'
2
+ import React from 'react'
3
+ import { MenuItem } from '../menu-item.js'
4
+
5
+ describe('Menu Component', () => {
6
+ it('Default menu item has role', () => {
7
+ const menuItemDataTest = 'data-test-menu-item'
8
+ const wrapper = mount(
9
+ <MenuItem dataTest={menuItemDataTest} label="Menu item" />
10
+ )
11
+ const menuItem = wrapper.find({ 'data-test': menuItemDataTest })
12
+ expect(menuItem.childAt(0).prop('role')).toBe('menuitem')
13
+ expect(menuItem.childAt(0).prop('aria-disabled')).toBe(undefined)
14
+ expect(menuItem.childAt(0).prop('aria-label')).toBe('Menu item')
15
+ })
16
+
17
+ it('Disabled menu item has aria-disabled attribute', () => {
18
+ const menuItemDataTest = 'data-test-menu-item'
19
+ const wrapper = mount(
20
+ <MenuItem
21
+ dataTest={menuItemDataTest}
22
+ label="Disabled menu item"
23
+ disabled={true}
24
+ />
25
+ )
26
+ const menuItem = wrapper.find({ 'data-test': menuItemDataTest })
27
+
28
+ expect(menuItem.childAt(0).prop('role')).toBe('menuitem')
29
+ expect(menuItem.childAt(0).prop('aria-disabled')).toBe(true)
30
+ expect(menuItem.childAt(0).prop('aria-label')).toBe(
31
+ 'Disabled menu item'
32
+ )
33
+ })
34
+
35
+ it('Toggle-able menu item has menuitemcheckbox role', () => {
36
+ const menuItemDataTest = 'data-test-menu-item'
37
+ const wrapper = mount(
38
+ <MenuItem
39
+ dataTest={menuItemDataTest}
40
+ label="Toggle-able menu item"
41
+ checkbox
42
+ checked={false}
43
+ />
44
+ )
45
+ const menuItem = wrapper.find({ 'data-test': menuItemDataTest })
46
+
47
+ expect(menuItem.childAt(0).prop('role')).not.toBe('menuitem')
48
+ expect(menuItem.childAt(0).prop('role')).toBe('menuitemcheckbox')
49
+ expect(menuItem.childAt(0).prop('aria-checked')).toBe(false)
50
+ expect(menuItem.childAt(0).prop('aria-label')).toBe(
51
+ 'Toggle-able menu item'
52
+ )
53
+ })
54
+
55
+ it('Submenu has relevant aria attributes', () => {
56
+ const showSubMenu = false
57
+ const wrapper = mount(
58
+ <MenuItem
59
+ showSubMenu={showSubMenu}
60
+ toggleSubMenu={() => jest.fn()}
61
+ label="Parent of submenus"
62
+ >
63
+ <MenuItem label="Test submenu child" />
64
+ </MenuItem>
65
+ )
66
+
67
+ const menuItem = wrapper.find({ role: 'menuitem' })
68
+
69
+ expect(menuItem.prop('aria-haspopup')).toBe('menu')
70
+ expect(menuItem.prop('aria-expanded')).toBe(false)
71
+ expect(menuItem.prop('aria-label')).toBe('Parent of submenus')
72
+ expect(wrapper.find({ label: 'Test submenu child' }).exists()).toBe(
73
+ false
74
+ )
75
+ })
76
+
77
+ describe('aria-label attribute', () => {
78
+ it('produces a log if the label prop is not a string', () => {
79
+ // Prep to test logs
80
+ const originalConsoleDebug = console.debug
81
+ const consoleDebugMock = jest.fn()
82
+ console.debug = consoleDebugMock
83
+
84
+ const menuItemDataTest = 'data-test-menu-item'
85
+ const wrapper = mount(
86
+ <MenuItem
87
+ dataTest={menuItemDataTest}
88
+ label={<span>{'Node label'}</span>}
89
+ />
90
+ )
91
+ const menuItem = wrapper.find({ 'data-test': menuItemDataTest })
92
+
93
+ expect(menuItem.childAt(0).prop('role')).toBe('menuitem')
94
+ expect(menuItem.childAt(0).prop('aria-label')).toBe(undefined)
95
+ expect(consoleDebugMock).toHaveBeenCalledWith(
96
+ 'The label prop on MenuItem is not a string; a value for the ariaLabel prop should be provided'
97
+ )
98
+
99
+ // Teardown
100
+ console.debug = originalConsoleDebug
101
+ })
102
+
103
+ it('uses the ariaLabel prop for aria-label if defined', () => {
104
+ const menuItemDataTest = 'data-test-menu-item'
105
+ const wrapper = mount(
106
+ <MenuItem
107
+ dataTest={menuItemDataTest}
108
+ label={<span>{'Node label'}</span>}
109
+ ariaLabel="Aria label"
110
+ />
111
+ )
112
+ const menuItem = wrapper.find({ 'data-test': menuItemDataTest })
113
+
114
+ expect(menuItem.childAt(0).prop('role')).toBe('menuitem')
115
+ expect(menuItem.childAt(0).prop('aria-label')).toBe('Aria label')
116
+ })
117
+ })
118
+ })
@@ -0,0 +1,11 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MenuItem with href is rendered', () => {
4
+ cy.visitStory('MenuItem', 'With Href')
5
+ })
6
+
7
+ Then('a link is rendered with the href', () => {
8
+ cy.get('#storybook-root a')
9
+ .should('have.attr', 'href')
10
+ .and('include', 'url.test')
11
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The MenuItem accepts a href prop
2
+
3
+ Scenario: A MenuItem with href
4
+ Given a MenuItem with href is rendered
5
+ Then a link is rendered with the href
@@ -0,0 +1,10 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MenuItem supplied with an icon is rendered', () => {
4
+ cy.visitStory('MenuItem', 'With Icon')
5
+ cy.get('[data-test="dhis2-uicore-menuitem"]').should('be.visible')
6
+ })
7
+
8
+ Then('the icon will be visible', () => {
9
+ cy.contains('Icon').should('be.visible')
10
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The MenuItem accepts an icon prop
2
+
3
+ Scenario: MenuItem renders supplied icon
4
+ Given a MenuItem supplied with an icon is rendered
5
+ Then the icon will be visible
@@ -0,0 +1,10 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MenuItem supplied with a label is rendered', () => {
4
+ cy.visitStory('MenuItem', 'With Label')
5
+ cy.get('[data-test="dhis2-uicore-menuitem"]').should('be.visible')
6
+ })
7
+
8
+ Then('the label is visible', () => {
9
+ cy.get(':contains("label")').should('be.visible')
10
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The MenuItem accepts a label prop
2
+
3
+ Scenario: MenuItem renders supplied label
4
+ Given a MenuItem supplied with a label is rendered
5
+ Then the label is visible
@@ -0,0 +1,10 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MenuItem supplied with a suffix is rendered', () => {
4
+ cy.visitStory('MenuItem', 'With Suffix')
5
+ cy.get('[data-test="dhis2-uicore-menuitem"]').should('be.visible')
6
+ })
7
+
8
+ Then('the suffix will be visible', () => {
9
+ cy.contains('Suffix').should('be.visible')
10
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The MenuItem accepts a suffix prop
2
+
3
+ Scenario: MenuItem renders supplied suffix
4
+ Given a MenuItem supplied with a suffix is rendered
5
+ Then the suffix will be visible
@@ -0,0 +1,11 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MenuItem with target is rendered', () => {
4
+ cy.visitStory('MenuItem', 'With Target')
5
+ })
6
+
7
+ Then('a link is rendered with the target', () => {
8
+ cy.get('#storybook-root a')
9
+ .should('have.attr', 'target')
10
+ .and('include', '_blank')
11
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The MenuItem accepts a target prop
2
+
3
+ Scenario: A MenuItem with target
4
+ Given a MenuItem with target is rendered
5
+ Then a link is rendered with the target
@@ -0,0 +1,17 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a MenuItem with onClick handler and value is rendered', () => {
4
+ cy.visitStory('MenuItem', 'With On Click And Value')
5
+ })
6
+
7
+ When('the MenuItem is clicked', () => {
8
+ cy.get('[data-test="dhis2-uicore-menuitem"]').click()
9
+ })
10
+
11
+ Then('the onClick handler is called with value', () => {
12
+ cy.window().should((win) => {
13
+ expect(win.onClick).to.be.calledWith({
14
+ value: 'Value',
15
+ })
16
+ })
17
+ })
@@ -0,0 +1,6 @@
1
+ Feature: The MenuItem has an onClick api
2
+
3
+ Scenario: The user clicks on the MenuItem
4
+ Given a MenuItem with onClick handler and value is rendered
5
+ When the MenuItem is clicked
6
+ Then the onClick handler is called with value
@@ -0,0 +1 @@
1
+ export { MenuItem } from './menu-item.js'
@@ -0,0 +1,29 @@
1
+ import React from 'react'
2
+ import { Menu } from '../index.js'
3
+ import { MenuItem } from './menu-item.js'
4
+
5
+ window.onClick = window.Cypress && window.Cypress.cy.stub()
6
+
7
+ export default {
8
+ title: 'MenuItem',
9
+ component: MenuItem,
10
+ decorators: [(story) => <Menu>{story()}</Menu>],
11
+ }
12
+
13
+ export const WithLabel = () => <MenuItem label="label" />
14
+
15
+ export const WithOnClickAndValue = () => (
16
+ <MenuItem value="Value" onClick={window.onClick} label="Menu item" />
17
+ )
18
+
19
+ export const WithHref = () => <MenuItem href="url.test" label="Menu item" />
20
+
21
+ export const WithTarget = () => (
22
+ <MenuItem href="url.test" target="_blank" label="Menu item" />
23
+ )
24
+
25
+ export const WithIcon = () => (
26
+ <MenuItem icon={<div>Icon</div>} label="Menu item" />
27
+ )
28
+
29
+ export const WithSuffix = () => <MenuItem suffix="Suffix" label="Menu item" />
@@ -0,0 +1,204 @@
1
+ import { IconChevronRight24 } from '@dhis2/ui-icons'
2
+ import { Popper } from '@dhis2-ui/popper'
3
+ import { Portal } from '@dhis2-ui/portal'
4
+ import cx from 'classnames'
5
+ import PropTypes from 'prop-types'
6
+ import React, { useEffect, useRef, useState, useMemo } from 'react'
7
+ import { FlyoutMenu } from '../flyout-menu/index.js'
8
+ import styles from './menu-item.styles.js'
9
+
10
+ const isModifiedEvent = (evt) =>
11
+ evt.metaKey || evt.altKey || evt.ctrlKey || evt.shiftKey
12
+
13
+ const createOnClickHandler =
14
+ ({ onClick, toggleSubMenu, isLink, value }) =>
15
+ (evt) => {
16
+ if ((isLink && isModifiedEvent(evt)) || !(onClick || toggleSubMenu)) {
17
+ return
18
+ }
19
+ evt.preventDefault()
20
+ evt.stopPropagation()
21
+
22
+ onClick && onClick({ value }, evt)
23
+ toggleSubMenu && toggleSubMenu()
24
+ }
25
+ const MenuItem = ({
26
+ href,
27
+ onClick,
28
+ children,
29
+ target,
30
+ icon,
31
+ className,
32
+ destructive,
33
+ disabled,
34
+ dense,
35
+ active,
36
+ dataTest = 'dhis2-uicore-menuitem',
37
+ chevron,
38
+ value,
39
+ label,
40
+ ariaLabel,
41
+ showSubMenu,
42
+ toggleSubMenu,
43
+ suffix,
44
+ checkbox,
45
+ checked,
46
+ tabIndex,
47
+ }) => {
48
+ const menuItemRef = useRef()
49
+ const [openSubMenus, setOpenSubMenus] = useState([])
50
+
51
+ useEffect(() => {
52
+ // track open submenus
53
+ setOpenSubMenus(document.querySelectorAll('[data-submenu-open=true]'))
54
+ }, [])
55
+
56
+ useEffect(() => {
57
+ if (!menuItemRef.current) {
58
+ return
59
+ }
60
+
61
+ const menuItem = menuItemRef.current
62
+
63
+ const handleKeyDown = (event) => {
64
+ const firstChild = event.target.children[0]
65
+ const hasSubMenu = firstChild?.getAttribute('aria-haspopup')
66
+ switch (event.key) {
67
+ // for submenus
68
+ case 'ArrowRight':
69
+ event.preventDefault()
70
+ if (hasSubMenu) {
71
+ firstChild.click()
72
+ }
73
+ break
74
+ case 'ArrowLeft':
75
+ case 'Escape': // close flyout menu
76
+ event.preventDefault()
77
+ openSubMenus[openSubMenus.length - 1]?.focus()
78
+ openSubMenus[openSubMenus.length - 1]?.children[0].click()
79
+ break
80
+ }
81
+ }
82
+
83
+ menuItem.addEventListener('keydown', handleKeyDown)
84
+
85
+ return () => {
86
+ menuItem.removeEventListener('keydown', handleKeyDown)
87
+ }
88
+ }, [openSubMenus])
89
+
90
+ const resolvedAriaLabel = useMemo(() => {
91
+ if (typeof label !== 'string' && ariaLabel === undefined) {
92
+ console.debug(
93
+ 'The label prop on MenuItem is not a string; a value for the ariaLabel prop should be provided'
94
+ )
95
+ }
96
+
97
+ return ariaLabel ?? (typeof label === 'string' ? label : undefined)
98
+ }, [ariaLabel, label])
99
+
100
+ return (
101
+ <>
102
+ <li
103
+ className={cx(className, {
104
+ destructive,
105
+ disabled,
106
+ dense,
107
+ active: active || showSubMenu,
108
+ 'with-chevron': children || chevron,
109
+ })}
110
+ ref={menuItemRef}
111
+ data-test={dataTest}
112
+ role="presentation"
113
+ tabIndex={tabIndex}
114
+ data-submenu-open={children && showSubMenu}
115
+ >
116
+ <a
117
+ target={target}
118
+ href={!disabled && href ? href : undefined}
119
+ onClick={
120
+ !disabled
121
+ ? createOnClickHandler({
122
+ onClick,
123
+ toggleSubMenu,
124
+ isLink: !!href,
125
+ value,
126
+ })
127
+ : undefined
128
+ }
129
+ role={checkbox ? 'menuitemcheckbox' : 'menuitem'}
130
+ aria-checked={checkbox ? checked : null}
131
+ aria-disabled={disabled}
132
+ aria-haspopup={children && 'menu'}
133
+ aria-expanded={showSubMenu}
134
+ aria-label={resolvedAriaLabel}
135
+ >
136
+ {icon && <span className="icon">{icon}</span>}
137
+
138
+ <span className="label">{label}</span>
139
+
140
+ {suffix && <span className="suffix">{suffix}</span>}
141
+
142
+ {(chevron || children) && (
143
+ <span className="chevron">
144
+ <IconChevronRight24 />
145
+ </span>
146
+ )}
147
+ </a>
148
+
149
+ <style jsx>{styles}</style>
150
+ </li>
151
+ {children && showSubMenu && (
152
+ <Portal>
153
+ <Popper placement="right-start" reference={menuItemRef}>
154
+ <FlyoutMenu dense={dense}>{children}</FlyoutMenu>
155
+ </Popper>
156
+ </Portal>
157
+ )}
158
+ </>
159
+ )
160
+ }
161
+
162
+ MenuItem.propTypes = {
163
+ active: PropTypes.bool,
164
+ /**
165
+ * By default, the label prop is used for the aria-label attribute on the
166
+ * underlying HTML element. If this prop is defined, it will be used as the
167
+ * aria-label instead
168
+ */
169
+ ariaLabel: PropTypes.string,
170
+ checkbox: PropTypes.bool,
171
+ checked: PropTypes.bool,
172
+ chevron: PropTypes.bool,
173
+ /**
174
+ * Nested menu items can become submenus.
175
+ * See `showSubMenu` and `toggleSubMenu` props, and 'Children' demo
176
+ */
177
+ children: PropTypes.node,
178
+ className: PropTypes.string,
179
+ dataTest: PropTypes.string,
180
+ dense: PropTypes.bool,
181
+ destructive: PropTypes.bool,
182
+ disabled: PropTypes.bool,
183
+ /** For using menu item as a link */
184
+ href: PropTypes.string,
185
+ /** An icon for the left side of the menu item */
186
+ icon: PropTypes.node,
187
+ /** Text in the menu item. If it's a string, will be used as aria-label */
188
+ label: PropTypes.node,
189
+ /** When true, nested menu items are shown in a Popper */
190
+ showSubMenu: PropTypes.bool,
191
+ /** A supporting element shown at the end of the menu item */
192
+ suffix: PropTypes.node,
193
+ tabIndex: PropTypes.number,
194
+ /** For using menu item as a link */
195
+ target: PropTypes.string,
196
+ /** On click, this function is called (without args) */
197
+ toggleSubMenu: PropTypes.func,
198
+ /** Value associated with item. Passed as an argument to onClick handler. */
199
+ value: PropTypes.string,
200
+ /** Click handler called with signature `({ value: string }, event)` */
201
+ onClick: PropTypes.func,
202
+ }
203
+
204
+ export { MenuItem }
@@ -0,0 +1,197 @@
1
+ import { Tag } from '@dhis2/ui'
2
+ import { colors } from '@dhis2/ui-constants'
3
+ import {
4
+ IconApps24,
5
+ IconVisualizationColumn24,
6
+ IconLaunch16,
7
+ } from '@dhis2/ui-icons'
8
+ import React, { useState } from 'react'
9
+ import { Menu } from '../index.js'
10
+ import { MenuItem } from './menu-item.js'
11
+
12
+ const description = `
13
+ Menu Items are intended to be children of Menu and Flyout Menu components. They can be nested to create submenus.
14
+
15
+ Splitting menus into several levels with child menus makes sense when there are a lot of options that can be grouped together. An example may be an option in level 1 menu of 'Download' that has several different download formats as child menu items. Make sure that child menu items relate to their parent item, otherwise a user will struggle to discover them. A menu item with children is not selectable/actionable itself, it serves only as a container for the child elements. Try to keep menus to a maximum of 1, 2 or 3 levels, anything more than this can easily confuse the user.
16
+
17
+ There is no enforced ordering of menu items, they should be presented in order of relevance. Put the most commonly used items at the top of the menu for easy discovery and access.
18
+
19
+ See the [design system](https://github.com/dhis2/design-system/blob/master/molecules/menu.md) for more information about menus.
20
+
21
+ \`\`\`js
22
+ import { MenuItem } from '@dhis2/ui'
23
+ \`\`\`
24
+ `
25
+
26
+ export default {
27
+ title: 'Menu Item',
28
+ component: MenuItem,
29
+ args: { label: 'Menu item' },
30
+ parameters: { docs: { description: { component: description } } },
31
+ }
32
+
33
+ const Template = (args) => (
34
+ <Menu>
35
+ <MenuItem {...args} />
36
+ </Menu>
37
+ )
38
+
39
+ export const Default = Template.bind({})
40
+
41
+ export const Active = Template.bind({})
42
+ Active.args = { active: true }
43
+
44
+ export const Chevron = Template.bind({})
45
+ Chevron.args = { chevron: true }
46
+
47
+ export const Dense = Template.bind({})
48
+ Dense.args = { dense: true }
49
+
50
+ export const Destructive = Template.bind({})
51
+ Destructive.args = { destructive: true }
52
+ Destructive.parameters = {
53
+ docs: {
54
+ description: {
55
+ story: "Destructive menu items should be used for critical, destructive actions such as 'Delete', 'Remove' or 'End process'. Do not use destructive menu items for actions that are simply important, they must also be destructive in nature. A menu should, ideally, only have one destructive action. Using a divider to separate normal and destructive options helps the user to understand that the destructive options is different from the rest of the options.",
56
+ },
57
+ },
58
+ }
59
+
60
+ export const Disabled = Template.bind({})
61
+ Disabled.args = { disabled: true }
62
+ Disabled.parameters = {
63
+ docs: {
64
+ description: {
65
+ story: 'Menu items should be disabled when they are not available, but could be available if something changes. Do not include menu items that will never be available, this will confuse a user. Instead, remove them from the menu.',
66
+ },
67
+ },
68
+ }
69
+
70
+ export const Link = Template.bind({})
71
+ Link.args = { target: '_blank', href: 'http://dhis2.org' }
72
+
73
+ export const Icon = (args) => {
74
+ // import { IconApps24 } from '@dhis2/ui'
75
+ return (
76
+ <Menu>
77
+ <MenuItem {...args} icon={<IconApps24 />} label="Menu item" />
78
+ <MenuItem
79
+ icon={<IconApps24 color="magenta" />}
80
+ label="Menu item - with custom icon fill"
81
+ />
82
+ </Menu>
83
+ )
84
+ }
85
+ Icon.parameters = {
86
+ docs: {
87
+ source: { type: 'code' },
88
+ description: {
89
+ story: 'A menu item can include an icon to help the user understand or recognize the option. An icon should support the menu item text and be simple enough to be understood in a dense UI. Icons add a lot of visual noise a menu, so only include them where they will help the user. Do not include icons only for visual reasons, the icon must functionally support the users understanding. Do not use complex icons. All menu items in a single menu do not need to have icons.',
90
+ },
91
+ },
92
+ }
93
+
94
+ export const InContainer = (args) => {
95
+ return (
96
+ <div
97
+ style={{
98
+ height: 40,
99
+ width: 300,
100
+ overflow: 'hidden',
101
+ }}
102
+ >
103
+ <Menu {...args}>
104
+ <MenuItem label="Menu item" />
105
+ </Menu>
106
+ </div>
107
+ )
108
+ }
109
+
110
+ export const Suffix = Template.bind({})
111
+ Suffix.args = {
112
+ label: 'Open in Data Visualizer',
113
+ icon: <IconVisualizationColumn24 color={colors.grey600} />,
114
+ suffix: <IconLaunch16 color={colors.grey600} />,
115
+ }
116
+
117
+ export const SuffixAndChevron = Template.bind({})
118
+ SuffixAndChevron.args = {
119
+ label: 'Security notifications',
120
+ chevron: true,
121
+ suffix: <Tag>3</Tag>,
122
+ }
123
+
124
+ export const OnClick = (args) => (
125
+ <Menu>
126
+ <MenuItem
127
+ onClick={(payload, event) => {
128
+ console.log(payload.value, event.target)
129
+ }}
130
+ value="myValue"
131
+ label="Menu item"
132
+ {...args}
133
+ />
134
+ </Menu>
135
+ )
136
+ OnClick.parameters = { docs: { source: { type: 'code' } } }
137
+ OnClick.args = { onClick: console.log }
138
+
139
+ export const ToggleMenuItem = (args) => {
140
+ const [on, setOn] = useState(false)
141
+ const toggleOn = () => setOn(!on)
142
+ const checkMarkStyle = { fontSize: '24px', lineHeight: '24px' }
143
+ const icon = on ? <span style={checkMarkStyle}>✓</span> : <span />
144
+
145
+ return (
146
+ <Menu>
147
+ <MenuItem
148
+ {...args}
149
+ onClick={toggleOn}
150
+ icon={icon}
151
+ label="A toggle menu item"
152
+ checkbox
153
+ checked={on}
154
+ />
155
+ </Menu>
156
+ )
157
+ }
158
+ ToggleMenuItem.parameters = { docs: { source: { type: 'code' } } }
159
+
160
+ export const SubMenus = (args) => {
161
+ const [showSubMenu, setShowSubMenu] = React.useState(false)
162
+ const toggleSubMenu = () => setShowSubMenu(!showSubMenu)
163
+
164
+ return (
165
+ <Menu>
166
+ <MenuItem
167
+ showSubMenu={showSubMenu}
168
+ toggleSubMenu={toggleSubMenu}
169
+ {...args}
170
+ label="Parent of submenus"
171
+ >
172
+ <MenuItem label="Submenu child 1" />
173
+ <MenuItem label="Submenu child 2" />
174
+ </MenuItem>
175
+ <MenuItem {...args} label="Regular item" />
176
+ </Menu>
177
+ )
178
+ }
179
+ SubMenus.parameters = {
180
+ docs: {
181
+ source: { type: 'code' },
182
+ description: {
183
+ story: "_View this story in the 'Canvas' tab for proper submenu alignment._",
184
+ },
185
+ },
186
+ }
187
+
188
+ export const RTLSuffixAndChevron = (args) => (
189
+ <div dir="rtl">
190
+ <Template {...args} />
191
+ </div>
192
+ )
193
+ RTLSuffixAndChevron.args = {
194
+ label: 'Security notifications',
195
+ chevron: true,
196
+ suffix: <Tag>3</Tag>,
197
+ }