Skip to content

Commit

Permalink
feat: refactor functionality to open and close submenus
Browse files Browse the repository at this point in the history
  • Loading branch information
d-rita committed May 7, 2024
1 parent d35ffb2 commit 9416c2f
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 48 deletions.
18 changes: 16 additions & 2 deletions components/menu/src/flyout-menu/flyout-menu.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { colors, elevations, spacers } from '@dhis2/ui-constants'
import PropTypes from 'prop-types'
import React, { Children, cloneElement, isValidElement, useState } from 'react'
import React, {
Children,
cloneElement,
isValidElement,
useEffect,
useRef,
useState,
} from 'react'
import { Menu } from '../index.js'

const FlyoutMenu = ({
Expand All @@ -16,9 +23,16 @@ const FlyoutMenu = ({
const toggleValue = index === openedSubMenu ? null : index
setOpenedSubMenu(toggleValue)
}
const ref = useRef(null)

useEffect(() => {
if (ref.current && ref.current?.children[0]) {
ref.current.children[0].focus()
}
}, [])

return (
<div className={className} data-test={dataTest}>
<div className={className} data-test={dataTest} ref={ref}>
<Menu dense={dense}>
{Children.map(children, (child, index) =>
isValidElement(child)
Expand Down
34 changes: 31 additions & 3 deletions components/menu/src/menu-item/menu-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Portal } from '@dhis2-ui/portal'
import { IconChevronRight24 } from '@dhis2/ui-icons'
import cx from 'classnames'
import PropTypes from 'prop-types'
import React, { forwardRef, useRef } from 'react'
import React, { forwardRef, useCallback, useRef, useState } from 'react'
import { FlyoutMenu } from '../index.js'
import styles from './menu-item.styles.js'

Expand Down Expand Up @@ -49,6 +49,32 @@ const MenuItem = forwardRef(function MenuItem(
ref
) {
const menuItemRef = useRef()
const [openSubMenu, setOpenSubmenu] = useState(false)
const [subMenu, setSubMenu] = useState([])

const handleKeyDown = useCallback(
(event) => {
setSubMenu(document.querySelectorAll('[data-submenu-open=true]'))
switch (event.key) {
case 'ArrowRight':
event.preventDefault()
if (event.target.getAttribute('aria-haspopup')) {
setOpenSubmenu(true)
}
break
case 'ArrowLeft':
event.preventDefault()
if (subMenu.length) {
subMenu[subMenu.length - 1].focus()
}
setOpenSubmenu(false)
break
default:
return
}
},
[subMenu]
)

return (
<>
Expand Down Expand Up @@ -86,14 +112,16 @@ const MenuItem = forwardRef(function MenuItem(
: 'menuitem'
}
aria-haspopup={children && 'menu'}
aria-expanded={showSubMenu}
aria-expanded={showSubMenu || openSubMenu}
aria-disabled={disabled}
aria-checked={checkbox && checked}
aria-label={label}
aria-owns={
children &&
`popper-${label.split(' ').join('-').toLowerCase()}`
}
data-submenu-open={children ? openSubMenu : null}
onKeyDown={handleKeyDown}
>
{icon && <span className="icon">{icon}</span>}

Expand All @@ -110,7 +138,7 @@ const MenuItem = forwardRef(function MenuItem(

<style jsx>{styles}</style>
</li>
{children && showSubMenu && (
{children && (showSubMenu || openSubMenu) && (
<Portal>
<Popper
placement="right-start"
Expand Down
68 changes: 25 additions & 43 deletions components/menu/src/menu/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,13 @@ const Menu = ({ children, className, dataTest, dense }) => {
return obj
}

const focusFirstFocusableItem = useCallback(
const handleFocus = useCallback(
(e) => {
const focusableItems = findFocusableItems()

if (e.target === menuRef.current) {
if (focusedIndex === -1) {
setFocusedIndex(~~Object.keys(focusableItems)[0])
// Object.values(focusableItems)[0].focus()
}
}
},
Expand Down Expand Up @@ -96,33 +95,12 @@ const Menu = ({ children, className, dataTest, dense }) => {
})
)
break
case 'ArrowRight':
event.preventDefault()
if (event.target.hasAttribute('aria-haspopup')) {
event.target.click()
}
break
case 'ArrowLeft':
event.preventDefault()
document
.querySelector(
`[aria-owns='${event.target.parentNode.parentNode.parentNode.parentNode.getAttribute(
'id'
)}']`
)
.focus()
document
.querySelector(
`[aria-owns='${event.target.parentNode.parentNode.parentNode.parentNode.getAttribute(
'id'
)}']`
)
.click()
break
case ' ':
case 'Enter':
event.preventDefault()
event.target.click()
if (event.target.getAttribute('aria-disabled') === null) {
event.target.click()
}
break
default:
return
Expand All @@ -144,14 +122,14 @@ const Menu = ({ children, className, dataTest, dense }) => {
}
const menu = menuRef.current

menu.addEventListener('focus', focusFirstFocusableItem)
menu.addEventListener('focus', handleFocus)
menu.addEventListener('keydown', handleKeyDown)

return () => {
menu.removeEventListener('keydown', handleKeyDown)
menu.removeEventListener('focus', focusFirstFocusableItem)
menu.removeEventListener('focus', handleFocus)
}
}, [focusFirstFocusableItem, handleKeyDown])
}, [handleFocus, handleKeyDown])

return (
<ul
Expand All @@ -178,20 +156,24 @@ const Menu = ({ children, className, dataTest, dense }) => {
showSubMenu: child.props.children
? child.props.showSubMenu
: null,
ref: (node) => {
const role = node?.getAttribute('role')
if (
[
'menuitem',
'menuitemradio',
'menuitemcheckbox',
].includes(role)
) {
return (itemsRefs.current[index] = node)
} else {
return null
}
},
...(child.props.dataTest &&
child.props.dataTest.includes('menuitem')
? {
ref: (node) => {
const role = node?.getAttribute('role')
if (
[
'menuitem',
'menuitemradio',
'menuitemcheckbox',
].includes(role)
) {
return (itemsRefs.current[index] =
node)
}
},
}
: {}),
})
: child
})}
Expand Down

0 comments on commit 9416c2f

Please sign in to comment.