Skip to content

Commit be3676e

Browse files
radglobgr2msiddharthkp
committed
fix: prevent closing menu when event.preventDefault() is called on ActionList.Item's onSelect handler by @gr2m (#3346)
* fix: prevent closing menu when `event.preventDefault()` is called on `ActionList.Item`'s `onSelect` handler (#3163) * test: prevent closing menu when `event.preventDefault()` is called on `ActionList.Item`'s `onSelect` handler Failing test for #3162 * fix: prevent closing menu when `event.preventDefault()` is called on `ActionList.Item`ߴs `onSelect` handler * add storybook example: Delayed Menu Close * update docs * docs: changeset * Update changelog --------- Co-authored-by: Siddharth Kshetrapal <siddharthkp@github.com> * Update generated/components.json --------- Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com> Co-authored-by: siddharthkp <siddharthkp@users.noreply.github.com>
1 parent 7e32d58 commit be3676e

File tree

6 files changed

+58
-13
lines changed

6 files changed

+58
-13
lines changed

.changeset/brave-cherries-march.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@primer/react": minor
3+
---
4+
5+
ActionMenu: Calling `event.preventDefault` inside `onSelect` of `ActionList.Item` will prevent the default behavior of closing the menu

generated/components.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
"name": "onSelect",
164164
"type": "(event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void",
165165
"defaultValue": "",
166-
"description": "Callback that is called when the item is selected using either the mouse or keyboard."
166+
"description": "Callback that is called when the item is selected using either the mouse or keyboard. `event.preventDefault()` will prevent a menu from closing when within an `<ActionMenu />`"
167167
},
168168
{
169169
"name": "selected",

src/ActionList/ActionList.docs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"name": "onSelect",
6363
"type": "(event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void",
6464
"defaultValue": "",
65-
"description": "Callback that is called when the item is selected using either the mouse or keyboard."
65+
"description": "Callback that is called when the item is selected using either the mouse or keyboard. `event.preventDefault()` will prevent a menu from closing when within an `<ActionMenu />`"
6666
},
6767
{
6868
"name": "selected",

src/ActionList/Item.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
2525
disabled = false,
2626
selected = undefined,
2727
active = false,
28-
onSelect,
28+
onSelect: onSelectUser,
2929
sx: sxProp = defaultSxProp,
3030
id,
3131
role,
@@ -46,6 +46,18 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
4646
const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
4747

4848
const selectionVariant = groupSelectionVariant ?? listSelectionVariant
49+
const onSelect = React.useCallback(
50+
(
51+
event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
52+
// eslint-disable-next-line @typescript-eslint/ban-types
53+
afterSelect?: Function,
54+
) => {
55+
if (typeof onSelectUser === 'function') onSelectUser(event)
56+
if (event.defaultPrevented) return
57+
if (typeof afterSelect === 'function') afterSelect()
58+
},
59+
[onSelectUser],
60+
)
4961

5062
/** Infer item role based on the container */
5163
let itemRole: ActionListItemProps['role']
@@ -163,22 +175,16 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
163175
const clickHandler = React.useCallback(
164176
(event: React.MouseEvent<HTMLLIElement>) => {
165177
if (disabled) return
166-
if (!event.defaultPrevented) {
167-
if (typeof onSelect === 'function') onSelect(event)
168-
// if this Item is inside a Menu, close the Menu
169-
if (typeof afterSelect === 'function') afterSelect()
170-
}
178+
onSelect(event, afterSelect)
171179
},
172180
[onSelect, disabled, afterSelect],
173181
)
174182

175183
const keyPressHandler = React.useCallback(
176184
(event: React.KeyboardEvent<HTMLLIElement>) => {
177185
if (disabled) return
178-
if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
179-
if (typeof onSelect === 'function') onSelect(event)
180-
// if this Item is inside a Menu, close the Menu
181-
if (typeof afterSelect === 'function') afterSelect()
186+
if ([' ', 'Enter'].includes(event.key)) {
187+
onSelect(event, afterSelect)
182188
}
183189
},
184190
[onSelect, disabled, afterSelect],

src/ActionMenu/ActionMenu.examples.stories.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
NumberIcon,
1212
CalendarIcon,
1313
XIcon,
14+
CheckIcon,
15+
CopyIcon,
1416
} from '@primer/octicons-react'
1517

1618
export default {
@@ -282,3 +284,35 @@ export const MultipleSections = () => {
282284
</ActionMenu>
283285
)
284286
}
287+
288+
export const DelayedMenuClose = () => {
289+
const [open, setOpen] = React.useState(false)
290+
const [copyLinkSuccess, setCopyLinkSuccess] = React.useState(false)
291+
const onSelect = (event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => {
292+
event.preventDefault()
293+
294+
setCopyLinkSuccess(true)
295+
setTimeout(() => {
296+
setOpen(false)
297+
setCopyLinkSuccess(false)
298+
}, 700)
299+
}
300+
301+
return (
302+
<>
303+
<h1>Delayed Menu Close</h1>
304+
305+
<ActionMenu open={open} onOpenChange={setOpen}>
306+
<ActionMenu.Button>Anchor</ActionMenu.Button>
307+
<ActionMenu.Overlay>
308+
<ActionList>
309+
<ActionList.Item onSelect={onSelect}>
310+
<ActionList.LeadingVisual>{copyLinkSuccess ? <CheckIcon /> : <CopyIcon />}</ActionList.LeadingVisual>
311+
{copyLinkSuccess ? 'Copied!' : 'Copy link'}
312+
</ActionList.Item>
313+
</ActionList>
314+
</ActionMenu.Overlay>
315+
</ActionMenu>
316+
</>
317+
)
318+
}

src/ActionMenu/ActionMenu.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function Example(): JSX.Element {
2222
<ActionList.Divider />
2323
<ActionList.Item>Copy link</ActionList.Item>
2424
<ActionList.Item>Edit file</ActionList.Item>
25-
<ActionList.Item variant="danger" onClick={event => event.preventDefault()}>
25+
<ActionList.Item variant="danger" onSelect={event => event.preventDefault()}>
2626
Delete file
2727
</ActionList.Item>
2828
<ActionList.LinkItem href="//github.com" title="anchor" aria-keyshortcuts="s">

0 commit comments

Comments
 (0)