Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.

Commit 3d7dd3c

Browse files
committed
fix(menu): close when click on menu button
Correctly close the menu when open when clicking on the menu button again. Fixes #48.
1 parent 44cdc9f commit 3d7dd3c

File tree

2 files changed

+44
-59
lines changed

2 files changed

+44
-59
lines changed

packages/core/src/Menu/BaseMenu.tsx

Lines changed: 20 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ import React, {
88
} from 'react'
99
import styled, { css } from 'styled-components'
1010

11-
import {
12-
useBoolean,
13-
useVisibleFocus,
14-
useClickOutside,
15-
} from 'react-hooks-shareable'
11+
import { useBoolean, useVisibleFocus } from 'react-hooks-shareable'
1612

1713
import { spacing, componentSize, opacity, shape } from '../designparams'
1814
import { remainder } from '../utils/math'
@@ -142,8 +138,6 @@ const MenuContainer = styled.div`
142138
overflow: auto;
143139
`
144140

145-
const MenuList = styled.div``
146-
147141
export const BaseMenuItem = styled.div<{
148142
readonly disabled?: boolean
149143
readonly keyboardSelect?: boolean
@@ -291,58 +285,27 @@ enum MenuKeys {
291285
/**
292286
* Add an escape listener to the actual rendered menu.
293287
*/
294-
295-
interface MenuWrapperProps extends BaseProps {
296-
readonly onClose: () => void
288+
export interface MenuListProps extends BaseProps {
289+
/**
290+
* Called when user pressed the escape button on the keyboard
291+
*/
292+
readonly onEscape: () => void
297293
/**
298294
* `class` to be passed to the component.
299295
*/
300296
readonly className?: string
301297
}
302-
export const MenuWrapper: React.FunctionComponent<MenuWrapperProps> = ({
303-
onClose,
298+
299+
export const MenuList: React.FC<MenuListProps> = ({
300+
onEscape,
304301
children,
305302
onPointerDown,
306303
...props
307304
}) => {
308305
// Close when pressing escape key.
309-
useEscapeListenerStack(onClose)
310-
311-
const menuRef = useRef<HTMLDivElement>(null)
312-
313-
const handleClickOutside = useClickOutside(onClose)
314-
315-
const handlePointerDown = useCallback(
316-
e => {
317-
onPointerDown?.(e)
318-
handleClickOutside(e)
319-
},
320-
[handleClickOutside, onPointerDown]
321-
)
306+
useEscapeListenerStack(onEscape)
322307

323-
// Close when an item was clicked
324-
const onClick = useCallback<React.MouseEventHandler>(
325-
event => {
326-
event.stopPropagation()
327-
// Remove focus from the menu button when closing the menu.
328-
if (document.activeElement instanceof HTMLElement) {
329-
document.activeElement.blur()
330-
}
331-
onClose()
332-
},
333-
[onClose]
334-
)
335-
336-
return (
337-
<MenuContainer
338-
onClick={onClick}
339-
onPointerDown={handlePointerDown}
340-
ref={menuRef}
341-
{...props}
342-
>
343-
{children}
344-
</MenuContainer>
345-
)
308+
return <MenuContainer {...props}>{children}</MenuContainer>
346309
}
347310

348311
export interface BaseItemProps {
@@ -559,17 +522,15 @@ export const BaseMenu = memo<BaseMenuProps>(
559522
anchorEl={anchorRef.current}
560523
onScroll={hideAndBlurMenu}
561524
>
562-
<MenuWrapper onClose={hideMenu}>
563-
<MenuList>
564-
{components.map((component, index) => (
565-
<BaseItem
566-
key={index}
567-
keyboardSelect={index === arrowIndex}
568-
{...component}
569-
/>
570-
))}
571-
</MenuList>
572-
</MenuWrapper>
525+
<MenuList onEscape={hideMenu} onClick={hideAndBlurMenu}>
526+
{components.map((component, index) => (
527+
<BaseItem
528+
key={index}
529+
keyboardSelect={index === arrowIndex}
530+
{...component}
531+
/>
532+
))}
533+
</MenuList>
573534
</PopOver>
574535
) : null}
575536
</Anchor>

packages/ui-tests/src/coreComponents/Menu.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ context('Menu', () => {
2525
cy.get(popup).should('not.exist')
2626
})
2727

28+
it('Should close the menu when clicking on the button again', () => {
29+
cy.get(menu1).eq(0).find('button').click()
30+
cy.get(popup).eq(0).should('exist').should('be.visible')
31+
32+
cy.get(menu1).eq(0).find('button').click()
33+
cy.get(popup).should('not.exist')
34+
})
35+
36+
it('Should close the menu when clicking outside', () => {
37+
cy.get(menu1).eq(0).find('button').click()
38+
cy.get(popup).eq(0).should('exist').should('be.visible')
39+
40+
cy.get('body').click(0, 0) // Click outside
41+
cy.get(popup).should('not.exist')
42+
})
43+
44+
it('Should close the menu on blur', () => {
45+
cy.get(menu1).eq(0).find('button').click()
46+
cy.get(popup).eq(0).should('exist').should('be.visible')
47+
48+
cy.get(menu1).eq(0).find('button').blur()
49+
cy.get(popup).should('not.exist')
50+
})
51+
2852
it('Click a disabled item should do nothing', () => {
2953
cy.get(menu1).eq(0).find('button').click()
3054
cy.get(popup).eq(0).find(menuItemEl).eq(2).should('have.attr', 'disabled')

0 commit comments

Comments
 (0)