@dhis2-ui/menu 10.16.1 → 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhis2-ui/menu",
3
- "version": "10.16.1",
3
+ "version": "10.16.3-alpha.1",
4
4
  "description": "UI Menu",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,19 +33,20 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@dhis2/prop-types": "^3.1.2",
36
- "@dhis2-ui/card": "10.16.1",
37
- "@dhis2-ui/divider": "10.16.1",
38
- "@dhis2-ui/layer": "10.16.1",
39
- "@dhis2-ui/popper": "10.16.1",
40
- "@dhis2-ui/portal": "10.16.1",
41
- "@dhis2/ui-constants": "10.16.1",
42
- "@dhis2/ui-icons": "10.16.1",
36
+ "@dhis2-ui/card": "10.16.3-alpha.1",
37
+ "@dhis2-ui/divider": "10.16.3-alpha.1",
38
+ "@dhis2-ui/layer": "10.16.3-alpha.1",
39
+ "@dhis2-ui/popper": "10.16.3-alpha.1",
40
+ "@dhis2-ui/portal": "10.16.3-alpha.1",
41
+ "@dhis2/ui-constants": "10.16.3-alpha.1",
42
+ "@dhis2/ui-icons": "10.16.3-alpha.1",
43
43
  "classnames": "^2.3.1",
44
44
  "prop-types": "^15.7.2"
45
45
  },
46
46
  "files": [
47
47
  "build",
48
- "types"
48
+ "types",
49
+ "src"
49
50
  ],
50
51
  "devDependencies": {
51
52
  "react": "^18.3.1",
@@ -0,0 +1,48 @@
1
+ import { render } from '@testing-library/react'
2
+ import { userEvent } from '@testing-library/user-event'
3
+ import React from 'react'
4
+ import { MenuItem } from '../../menu-item/menu-item.js'
5
+ import { FlyoutMenu } from '../flyout-menu.js'
6
+
7
+ describe('Flyout Menu Component', () => {
8
+ it('can handle navigation of submenus', async () => {
9
+ const { getByText, queryByText, getAllByRole } = render(
10
+ <FlyoutMenu>
11
+ <MenuItem label="Item 1" />
12
+ <MenuItem label="Item 2">
13
+ <MenuItem label="Item 2 a" />
14
+ </MenuItem>
15
+ </FlyoutMenu>
16
+ )
17
+
18
+ const itemOne = getByText(/Item 1/i)
19
+ const itemTwo = getByText(/Item 2/i)
20
+ let submenuChild = queryByText(/Item 2 a/i)
21
+
22
+ const menuItems = getAllByRole('menuitem')
23
+
24
+ expect(menuItems.length).toBe(2)
25
+ expect(menuItems[0]).toBe(itemOne.parentNode)
26
+ expect(menuItems[1]).toBe(itemTwo.parentNode)
27
+
28
+ expect(submenuChild).not.toBeInTheDocument()
29
+
30
+ await userEvent.tab()
31
+ expect(menuItems[0].parentNode).toHaveFocus()
32
+ expect(menuItems[1].parentNode).not.toHaveFocus()
33
+
34
+ await userEvent.keyboard('{ArrowDown}')
35
+ expect(menuItems[0].parentNode).not.toHaveFocus()
36
+ expect(menuItems[1].parentNode).toHaveFocus()
37
+
38
+ await userEvent.keyboard('{ArrowRight}')
39
+ submenuChild = getByText(/Item 2 a/i)
40
+
41
+ expect(submenuChild).toBeInTheDocument()
42
+ expect(submenuChild.parentElement.parentElement).toHaveFocus()
43
+
44
+ await userEvent.keyboard('{ArrowLeft}')
45
+ expect(queryByText(/Item 2 a/i)).not.toBeInTheDocument()
46
+ expect(menuItems[1].parentNode).toHaveFocus()
47
+ })
48
+ })
@@ -0,0 +1,10 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a FlyoutMenu with children is rendered', () => {
4
+ cy.visitStory('FlyoutMenu', 'With Children')
5
+ cy.get('[data-test="dhis2-uicore-menu"]').should('be.visible')
6
+ })
7
+
8
+ Then('the children are visible', () => {
9
+ cy.contains('I am a child').should('be.visible')
10
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The FlyoutMenu renders children
2
+
3
+ Scenario: A FlyoutMenu with children
4
+ Given a FlyoutMenu with children is rendered
5
+ Then the children are visible
@@ -0,0 +1,80 @@
1
+ import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ const CLOSE_TO_DELTA = 1
4
+
5
+ Given(
6
+ 'there is enough space to the right of the MenuItem to fit the SubMenu',
7
+ () => {
8
+ cy.visitStory('FlyoutMenu', 'Default Position')
9
+ }
10
+ )
11
+
12
+ Given(
13
+ 'there is not enough space to the right of the MenuItem to fit the SubMenu',
14
+ () => {
15
+ cy.visitStory('FlyoutMenu', 'Flipped Position')
16
+ }
17
+ )
18
+
19
+ Given(
20
+ 'there is not enough space to the right or the left of the MenuItem to fit the SubMenu',
21
+ () => {
22
+ cy.visitStory('FlyoutMenu', 'Shift Into View')
23
+ }
24
+ )
25
+
26
+ When('the user clicks on the MenuItem', () => {
27
+ cy.get('[data-test="dhis2-uicore-menuitem"]').click()
28
+ })
29
+
30
+ Then(
31
+ 'the right of the MenuItem is aligned with the left of the SubMenu',
32
+ () => {
33
+ getMenuItemAndSubMenuRects().should(([menuItemRect, subMenuRect]) => {
34
+ expect(menuItemRect.right).to.closeTo(
35
+ subMenuRect.left,
36
+ CLOSE_TO_DELTA
37
+ )
38
+ })
39
+ }
40
+ )
41
+
42
+ Then(
43
+ 'the left of the MenuItem is aligned with the right of the SubMenu',
44
+ () => {
45
+ getMenuItemAndSubMenuRects().should(([menuItemRect, subMenuRect]) => {
46
+ expect(menuItemRect.left).to.closeTo(
47
+ subMenuRect.right,
48
+ CLOSE_TO_DELTA
49
+ )
50
+ })
51
+ }
52
+ )
53
+
54
+ Then('the SubMenu is rendered on top of the MenuItem', () => {
55
+ getMenuItemAndSubMenuRects().should(([menuItemRect, subMenuRect]) => {
56
+ expect(subMenuRect.left).to.be.at.most(menuItemRect.right)
57
+ expect(subMenuRect.right).to.be.at.least(menuItemRect.left)
58
+ expect(subMenuRect.top).to.be.at.most(menuItemRect.bottom)
59
+ expect(subMenuRect.bottom).to.be.at.least(menuItemRect.top)
60
+ })
61
+ })
62
+
63
+ Then(
64
+ 'the top of the MenuItem is aligned with the top of the SubMenu wrapper',
65
+ () => {
66
+ cy.getPositionsBySelectors(
67
+ '[data-test="dhis2-uicore-menuitem"]',
68
+ '[data-test="dhis2-uicore-popper"]'
69
+ ).should(([menuItemRect, popperRect]) => {
70
+ expect(menuItemRect.top).to.closeTo(popperRect.top, CLOSE_TO_DELTA)
71
+ })
72
+ }
73
+ )
74
+
75
+ function getMenuItemAndSubMenuRects() {
76
+ return cy.getPositionsBySelectors(
77
+ '[data-test="dhis2-uicore-menuitem"]',
78
+ '[data-test="dhis2-uicore-popper"]'
79
+ )
80
+ }
@@ -0,0 +1,19 @@
1
+ Feature: Position of a SubMenu component
2
+
3
+ Scenario: Default rendering
4
+ Given there is enough space to the right of the MenuItem to fit the SubMenu
5
+ When the user clicks on the MenuItem
6
+ Then the right of the MenuItem is aligned with the left of the SubMenu
7
+ And the top of the MenuItem is aligned with the top of the SubMenu wrapper
8
+
9
+ Scenario: Flipped rendering when insufficient space to the right of
10
+ Given there is not enough space to the right of the MenuItem to fit the SubMenu
11
+ When the user clicks on the MenuItem
12
+ Then the left of the MenuItem is aligned with the right of the SubMenu
13
+ And the top of the MenuItem is aligned with the top of the SubMenu wrapper
14
+
15
+ Scenario: Shifting into view when insufficient space to the right of and above
16
+ Given there is not enough space to the right or the left of the MenuItem to fit the SubMenu
17
+ When the user clicks on the MenuItem
18
+ Then the SubMenu is rendered on top of the MenuItem
19
+ And the top of the MenuItem is aligned with the top of the SubMenu wrapper
@@ -0,0 +1,26 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a FlyoutMenu with two SubMenus is rendered', () => {
4
+ cy.visitStory('FlyoutMenu', 'Toggles Sub Menus')
5
+ cy.get('[data-test="dhis2-uicore-menu"]').should('be.visible')
6
+ })
7
+
8
+ When('the user clicks the first SubMenu anchor', () => {
9
+ cy.contains('Item 1').click()
10
+ })
11
+
12
+ Then('first SubMenu is visible', () => {
13
+ cy.contains('SubMenu 1').should('be.visible')
14
+ })
15
+
16
+ When('the user clicks the second SubMenu anchor', () => {
17
+ cy.contains('Item 2').click()
18
+ })
19
+
20
+ Then('second SubMenu is visible', () => {
21
+ cy.contains('SubMenu 2').should('be.visible')
22
+ })
23
+
24
+ Then('the first SubMenu is not rendered', () => {
25
+ cy.contains('SubMenu 1').should('not.exist')
26
+ })
@@ -0,0 +1,9 @@
1
+ Feature: The FlyoutMenu toggles SubMenus
2
+
3
+ Scenario: a FlyoutMenu with two SubMenus
4
+ Given a FlyoutMenu with two SubMenus is rendered
5
+ When the user clicks the first SubMenu anchor
6
+ Then first SubMenu is visible
7
+ When the user clicks the second SubMenu anchor
8
+ Then second SubMenu is visible
9
+ And the first SubMenu is not rendered
@@ -0,0 +1,55 @@
1
+ import { MenuItem } from '@dhis2-ui/menu'
2
+ import React from 'react'
3
+ import { FlyoutMenu } from './flyout-menu.js'
4
+
5
+ export default {
6
+ title: 'FlyoutMenu',
7
+ component: FlyoutMenu,
8
+ }
9
+
10
+ const MenuItemSubMenuPositions = () => (
11
+ <FlyoutMenu>
12
+ <MenuItem label="Item 1">
13
+ <div style={{ padding: 30 }}>SubMenu 1</div>
14
+ </MenuItem>
15
+ </FlyoutMenu>
16
+ )
17
+
18
+ export const WithChildren = () => <FlyoutMenu>I am a child</FlyoutMenu>
19
+
20
+ export const TogglesSubMenus = () => (
21
+ <FlyoutMenu>
22
+ <MenuItem label="Item 1">SubMenu 1</MenuItem>
23
+ <MenuItem label="Item 2">SubMenu 2</MenuItem>
24
+ </FlyoutMenu>
25
+ )
26
+
27
+ export const DefaultPosition = () => <MenuItemSubMenuPositions />
28
+ export const FlippedPosition = () => (
29
+ <div>
30
+ <MenuItemSubMenuPositions />
31
+ <style jsx>{`
32
+ div {
33
+ text-align: right;
34
+ }
35
+ `}</style>
36
+ </div>
37
+ )
38
+ export const ShiftIntoView = () => (
39
+ <div>
40
+ <MenuItemSubMenuPositions />
41
+ <style jsx>{`
42
+ :global(html),
43
+ :global(body),
44
+ :global(#root) {
45
+ position: relative;
46
+ width: 200px;
47
+ max-width: 200px;
48
+ overflow: hidden;
49
+ }
50
+ :global(#root) {
51
+ border: 3px dashed grey;
52
+ }
53
+ `}</style>
54
+ </div>
55
+ )
@@ -0,0 +1,108 @@
1
+ import { colors, elevations, spacers } from '@dhis2/ui-constants'
2
+ import PropTypes from 'prop-types'
3
+ import React, {
4
+ Children,
5
+ cloneElement,
6
+ isValidElement,
7
+ useEffect,
8
+ useRef,
9
+ useState,
10
+ } from 'react'
11
+ import { Menu } from '../menu/index.js'
12
+
13
+ const FlyoutMenu = ({
14
+ children,
15
+ className,
16
+ dataTest = 'dhis2-uicore-menu',
17
+ dense,
18
+ maxHeight = 'auto',
19
+ maxWidth = '380px',
20
+ closeMenu,
21
+ }) => {
22
+ const [openedSubMenu, setOpenedSubMenu] = useState(null)
23
+ const toggleSubMenu = (index) => {
24
+ const toggleValue = index === openedSubMenu ? null : index
25
+ setOpenedSubMenu(toggleValue)
26
+ }
27
+
28
+ const divRef = useRef(null)
29
+
30
+ useEffect(() => {
31
+ if (!divRef.current) {
32
+ return
33
+ }
34
+ const div = divRef.current
35
+
36
+ const handleFocus = (event) => {
37
+ if (event.target === div) {
38
+ if (div?.children && div.children.length > 0) {
39
+ div.children[0].focus()
40
+ }
41
+ }
42
+ }
43
+
44
+ const handleKeyDown = (event) => {
45
+ if (event.key === 'Escape') {
46
+ event.preventDefault()
47
+ closeMenu && closeMenu()
48
+ }
49
+ }
50
+
51
+ div.addEventListener('focus', handleFocus)
52
+ div.addEventListener('keydown', handleKeyDown)
53
+
54
+ return () => {
55
+ div.removeEventListener('focus', handleFocus)
56
+ div.removeEventListener('keydown', handleKeyDown)
57
+ }
58
+ }, [closeMenu])
59
+
60
+ return (
61
+ <div
62
+ className={className}
63
+ data-test={dataTest}
64
+ tabIndex={0}
65
+ ref={divRef}
66
+ >
67
+ <Menu dense={dense}>
68
+ {Children.map(children, (child, index) =>
69
+ isValidElement(child)
70
+ ? cloneElement(child, {
71
+ showSubMenu: openedSubMenu === index,
72
+ toggleSubMenu: toggleSubMenu.bind(this, index),
73
+ })
74
+ : child
75
+ )}
76
+ </Menu>
77
+
78
+ <style jsx>{`
79
+ div {
80
+ background: ${colors.white};
81
+ border-radius: 3px;
82
+ box-shadow: ${elevations.popover};
83
+ display: inline-block;
84
+ min-width: ${dense ? '128' : '180'}px;
85
+ max-width: ${maxWidth};
86
+ max-height: ${maxHeight};
87
+ padding: ${spacers.dp4} 0;
88
+ overflow: auto;
89
+ }
90
+ `}</style>
91
+ </div>
92
+ )
93
+ }
94
+
95
+ FlyoutMenu.propTypes = {
96
+ /** Typically, but not limited to, `MenuItem` components */
97
+ children: PropTypes.node,
98
+ className: PropTypes.string,
99
+ /** when Escape key is pressed, this function is called to close the flyout menu */
100
+ closeMenu: PropTypes.func,
101
+ dataTest: PropTypes.string,
102
+ /** Menu uses smaller dimensions */
103
+ dense: PropTypes.bool,
104
+ maxHeight: PropTypes.string,
105
+ maxWidth: PropTypes.string,
106
+ }
107
+
108
+ export { FlyoutMenu }
@@ -0,0 +1,229 @@
1
+ import { IconChevronDown16 } from '@dhis2/ui-icons'
2
+ import { Layer } from '@dhis2-ui/layer'
3
+ import { MenuDivider, MenuItem, MenuSectionHeader } from '@dhis2-ui/menu'
4
+ import { Popper } from '@dhis2-ui/popper'
5
+ import React, { useState, useRef } from 'react'
6
+ import { FlyoutMenu } from './flyout-menu.js'
7
+
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
+ `
26
+
27
+ export default {
28
+ title: 'Flyout Menu',
29
+ component: FlyoutMenu,
30
+ parameters: { docs: { description: { component: description } } },
31
+ }
32
+
33
+ export const Default = (args) => (
34
+ <FlyoutMenu {...args}>
35
+ <MenuItem label="Item 1" />
36
+ <MenuItem label="Item 2" />
37
+ </FlyoutMenu>
38
+ )
39
+
40
+ export const Dense = Default.bind({})
41
+ Dense.args = { dense: true }
42
+
43
+ export const MaxHeight = (args) => (
44
+ <FlyoutMenu {...args}>
45
+ <MenuItem label="Item 1" />
46
+ <MenuItem label="Item 2" />
47
+ <MenuItem label="Item 3" />
48
+ <MenuItem label="Item 4" />
49
+ <MenuItem label="Item 5" />
50
+ <MenuItem label="Item 6" />
51
+ <MenuItem label="Item 7" />
52
+ <MenuItem label="Item 8" />
53
+ <MenuItem label="Item 9" />
54
+ <MenuItem label="Item 10" />
55
+ </FlyoutMenu>
56
+ )
57
+ MaxHeight.args = { maxHeight: '250px' }
58
+
59
+ export const MaxWidth = (args) => (
60
+ <>
61
+ <FlyoutMenu>
62
+ <MenuItem label="Short item 1" />
63
+ <MenuItem label="Short item 2" />
64
+ </FlyoutMenu>
65
+ <br />
66
+ <FlyoutMenu>
67
+ <MenuItem
68
+ label="Item 1 - with a lot of text and using a default maxWidth value
69
+ of 380px"
70
+ />
71
+ <MenuItem
72
+ label="Item 2 - with a lot of text and using a default maxWidth value
73
+ of 380px"
74
+ />
75
+ </FlyoutMenu>
76
+ <br />
77
+ <FlyoutMenu {...args}>
78
+ <MenuItem
79
+ label={`Item 1 - with a lot of text and using a custom maxWidth value of
80
+ ${args.maxWidth}`}
81
+ />
82
+ <MenuItem
83
+ label={`Item 2 - with a lot of text and using a custom maxWidth value of
84
+ ${args.maxWidth}`}
85
+ />
86
+ </FlyoutMenu>
87
+ </>
88
+ )
89
+ MaxWidth.args = { maxWidth: '300px' }
90
+
91
+ export const WithSubMenus = (args) => (
92
+ <FlyoutMenu {...args}>
93
+ <MenuItem label="Item 1" />
94
+ <MenuItem label="Item 2">
95
+ <MenuItem label="Item 2 a" />
96
+ <MenuItem label="Item 2 b">
97
+ <MenuItem label="Item 2 b i" />
98
+ <MenuItem label="Item 2 b ii" />
99
+ </MenuItem>
100
+ <MenuItem label="Item 2 c" />
101
+ </MenuItem>
102
+ <MenuItem label="Item 3" />
103
+ <MenuItem label="Item 4">
104
+ <MenuItem label="Item 4 a" />
105
+ <MenuItem label="Item 4 b">
106
+ <MenuItem label="Item 4 b i" />
107
+ <MenuItem label="Item 4 b ii" />
108
+ </MenuItem>
109
+ <MenuItem label="Item 4 c" />
110
+ </MenuItem>
111
+ <MenuItem label="Item 5" />
112
+ </FlyoutMenu>
113
+ )
114
+ WithSubMenus.parameters = {
115
+ docs: {
116
+ description: {
117
+ story: 'See this demo in the Canvas tab for proper alignment of sub menus.',
118
+ },
119
+ },
120
+ }
121
+
122
+ export const WithVariousChildren = (args) => (
123
+ <FlyoutMenu {...args}>
124
+ <MenuSectionHeader label="Section with sub-menus" />
125
+ <MenuItem label="Item 1" />
126
+ <MenuItem label="Item 2">
127
+ <MenuItem label="Item 2 a" />
128
+ <MenuItem label="Item 2 b">
129
+ <MenuItem label="Item 2 b i" />
130
+ <MenuItem label="Item 2 b ii" />
131
+ </MenuItem>
132
+ <MenuItem label="Item 2 c" />
133
+ </MenuItem>
134
+ <MenuItem label="Item 3" />
135
+ <MenuItem label="Item 4">
136
+ <MenuItem label="Item 4 a" />
137
+ <MenuItem label="Item 4 b">
138
+ <MenuItem label="Item 4 b i" />
139
+ <MenuItem label="Item 4 b ii" />
140
+ </MenuItem>
141
+ <MenuItem label="Item 4 c" />
142
+ </MenuItem>
143
+ <MenuItem label="Item 5" />
144
+ <MenuSectionHeader label="Section with dividers between menu items" />
145
+ <MenuItem label="Item 1" />
146
+ <MenuDivider />
147
+ <MenuItem label="Item 2" />
148
+ <MenuDivider />
149
+ <MenuItem label="Item 3" />
150
+ </FlyoutMenu>
151
+ )
152
+ WithVariousChildren.parameters = {
153
+ docs: {
154
+ description: {
155
+ story: 'See this demo in the Canvas tab for proper alignment of sub menus.',
156
+ },
157
+ },
158
+ }
159
+
160
+ export const DropDownMenu = (args) => {
161
+ const ref = useRef()
162
+ const [open, setOpen] = useState(false)
163
+ const toggle = () => setOpen(!open)
164
+
165
+ return (
166
+ <>
167
+ <button ref={ref} onClick={toggle}>
168
+ Open menu &nbsp;&nbsp; <IconChevronDown16 />
169
+ </button>
170
+ {open && (
171
+ <Layer onBackdropClick={toggle}>
172
+ <Popper reference={ref} placement="bottom-start">
173
+ <FlyoutMenu {...args} closeMenu={toggle}>
174
+ <MenuItem label="Item 1" />
175
+ <MenuItem label="Item 2" />
176
+ </FlyoutMenu>
177
+ </Popper>
178
+ </Layer>
179
+ )}
180
+ </>
181
+ )
182
+ }
183
+ DropDownMenu.parameters = {
184
+ docs: {
185
+ description: {
186
+ story: 'View this demo in the canvas to see the dropdown menu.',
187
+ },
188
+ source: { type: 'code' },
189
+ },
190
+ }
191
+
192
+ export const WithCustomMenuItem = (args) => {
193
+ // You should not create custom components in the render cycle
194
+ // this is just for demo purposes
195
+ const PopupWindowMenuItem = ({ to, children, ...rest }) => {
196
+ const HEIGHT = 1000
197
+ const WIDTH = 1400
198
+ const centerY = (window.screen.height - HEIGHT) / 2
199
+ const centerX = (window.screen.width - WIDTH) / 2
200
+
201
+ const onClick = () =>
202
+ window.open(
203
+ to,
204
+ 'Popup',
205
+ [
206
+ 'menubar=no',
207
+ 'location=no',
208
+ 'resizable=no',
209
+ 'scrollbars=no',
210
+ 'status=no',
211
+ `width=${WIDTH}`,
212
+ `height=${HEIGHT}`,
213
+ `top=${centerY}`,
214
+ `left=${centerX}`,
215
+ ].join()
216
+ )
217
+
218
+ return <MenuItem onClick={onClick} label={children} {...rest} />
219
+ }
220
+
221
+ return (
222
+ <FlyoutMenu {...args}>
223
+ <MenuItem label="A normal menu item" />
224
+ <PopupWindowMenuItem to="http://dhis2.org">
225
+ A custom menu item (popup window)
226
+ </PopupWindowMenuItem>
227
+ </FlyoutMenu>
228
+ )
229
+ }
@@ -0,0 +1 @@
1
+ export { FlyoutMenu } from './flyout-menu.js'
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { MenuDivider } from './menu-divider/index.js'
2
+ export { MenuItem } from './menu-item/index.js'
3
+ export { MenuSectionHeader } from './menu-section-header/index.js'
4
+
5
+ export { Menu } from './menu/index.js'
6
+ export { FlyoutMenu } from './flyout-menu/index.js'