Skip to content

Commit 6304923

Browse files
siddharthkpgr2m
andauthored
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 1665a1b commit 6304923

File tree

6 files changed

+59
-13
lines changed

6 files changed

+59
-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: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
2424
disabled = false,
2525
selected = undefined,
2626
active = false,
27-
onSelect,
27+
onSelect: onSelectUser,
2828
sx: sxProp = defaultSxProp,
2929
id,
3030
role,
@@ -42,6 +42,19 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
4242
const {container, afterSelect, selectionAttribute} = React.useContext(ActionListContainerContext)
4343
const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
4444

45+
const onSelect = React.useCallback(
46+
(
47+
event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
48+
// eslint-disable-next-line @typescript-eslint/ban-types
49+
afterSelect?: Function,
50+
) => {
51+
if (typeof onSelectUser === 'function') onSelectUser(event)
52+
if (event.defaultPrevented) return
53+
if (typeof afterSelect === 'function') afterSelect()
54+
},
55+
[onSelectUser],
56+
)
57+
4558
const selectionVariant: ActionListProps['selectionVariant'] = groupSelectionVariant
4659
? groupSelectionVariant
4760
: listSelectionVariant
@@ -149,22 +162,16 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
149162
const clickHandler = React.useCallback(
150163
(event: React.MouseEvent<HTMLLIElement>) => {
151164
if (disabled) return
152-
if (!event.defaultPrevented) {
153-
if (typeof onSelect === 'function') onSelect(event)
154-
// if this Item is inside a Menu, close the Menu
155-
if (typeof afterSelect === 'function') afterSelect()
156-
}
165+
onSelect(event, afterSelect)
157166
},
158167
[onSelect, disabled, afterSelect],
159168
)
160169

161170
const keyPressHandler = React.useCallback(
162171
(event: React.KeyboardEvent<HTMLLIElement>) => {
163172
if (disabled) return
164-
if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
165-
if (typeof onSelect === 'function') onSelect(event)
166-
// if this Item is inside a Menu, close the Menu
167-
if (typeof afterSelect === 'function') afterSelect()
173+
if ([' ', 'Enter'].includes(event.key)) {
174+
onSelect(event, afterSelect)
168175
}
169176
},
170177
[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/__tests__/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)