Skip to content

Commit

Permalink
fix: prevent closing menu when event.preventDefault() is called on …
Browse files Browse the repository at this point in the history
…`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>
  • Loading branch information
3 people committed May 31, 2023
1 parent 1665a1b commit 6304923
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-cherries-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

ActionMenu: Calling `event.preventDefault` inside `onSelect` of `ActionList.Item` will prevent the default behavior of closing the menu
2 changes: 1 addition & 1 deletion generated/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"name": "onSelect",
"type": "(event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void",
"defaultValue": "",
"description": "Callback that is called when the item is selected using either the mouse or keyboard."
"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 />`"
},
{
"name": "selected",
Expand Down
2 changes: 1 addition & 1 deletion src/ActionList/ActionList.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"name": "onSelect",
"type": "(event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void",
"defaultValue": "",
"description": "Callback that is called when the item is selected using either the mouse or keyboard."
"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 />`"
},
{
"name": "selected",
Expand Down
27 changes: 17 additions & 10 deletions src/ActionList/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
disabled = false,
selected = undefined,
active = false,
onSelect,
onSelect: onSelectUser,
sx: sxProp = defaultSxProp,
id,
role,
Expand All @@ -42,6 +42,19 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
const {container, afterSelect, selectionAttribute} = React.useContext(ActionListContainerContext)
const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)

const onSelect = React.useCallback(
(
event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
// eslint-disable-next-line @typescript-eslint/ban-types
afterSelect?: Function,
) => {
if (typeof onSelectUser === 'function') onSelectUser(event)
if (event.defaultPrevented) return
if (typeof afterSelect === 'function') afterSelect()
},
[onSelectUser],
)

const selectionVariant: ActionListProps['selectionVariant'] = groupSelectionVariant
? groupSelectionVariant
: listSelectionVariant
Expand Down Expand Up @@ -149,22 +162,16 @@ export const Item = React.forwardRef<HTMLLIElement, ActionListItemProps>(
const clickHandler = React.useCallback(
(event: React.MouseEvent<HTMLLIElement>) => {
if (disabled) return
if (!event.defaultPrevented) {
if (typeof onSelect === 'function') onSelect(event)
// if this Item is inside a Menu, close the Menu
if (typeof afterSelect === 'function') afterSelect()
}
onSelect(event, afterSelect)
},
[onSelect, disabled, afterSelect],
)

const keyPressHandler = React.useCallback(
(event: React.KeyboardEvent<HTMLLIElement>) => {
if (disabled) return
if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
if (typeof onSelect === 'function') onSelect(event)
// if this Item is inside a Menu, close the Menu
if (typeof afterSelect === 'function') afterSelect()
if ([' ', 'Enter'].includes(event.key)) {
onSelect(event, afterSelect)
}
},
[onSelect, disabled, afterSelect],
Expand Down
34 changes: 34 additions & 0 deletions src/ActionMenu/ActionMenu.examples.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
NumberIcon,
CalendarIcon,
XIcon,
CheckIcon,
CopyIcon,
} from '@primer/octicons-react'

export default {
Expand Down Expand Up @@ -282,3 +284,35 @@ export const MultipleSections = () => {
</ActionMenu>
)
}

export const DelayedMenuClose = () => {
const [open, setOpen] = React.useState(false)
const [copyLinkSuccess, setCopyLinkSuccess] = React.useState(false)
const onSelect = (event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => {
event.preventDefault()

setCopyLinkSuccess(true)
setTimeout(() => {
setOpen(false)
setCopyLinkSuccess(false)
}, 700)
}

return (
<>
<h1>Delayed Menu Close</h1>

<ActionMenu open={open} onOpenChange={setOpen}>
<ActionMenu.Button>Anchor</ActionMenu.Button>
<ActionMenu.Overlay>
<ActionList>
<ActionList.Item onSelect={onSelect}>
<ActionList.LeadingVisual>{copyLinkSuccess ? <CheckIcon /> : <CopyIcon />}</ActionList.LeadingVisual>
{copyLinkSuccess ? 'Copied!' : 'Copy link'}
</ActionList.Item>
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
</>
)
}
2 changes: 1 addition & 1 deletion src/__tests__/ActionMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function Example(): JSX.Element {
<ActionList.Divider />
<ActionList.Item>Copy link</ActionList.Item>
<ActionList.Item>Edit file</ActionList.Item>
<ActionList.Item variant="danger" onClick={event => event.preventDefault()}>
<ActionList.Item variant="danger" onSelect={event => event.preventDefault()}>
Delete file
</ActionList.Item>
<ActionList.LinkItem href="//github.com" title="anchor" aria-keyshortcuts="s">
Expand Down

0 comments on commit 6304923

Please sign in to comment.