diff --git a/.changeset/twelve-jeans-kneel.md b/.changeset/twelve-jeans-kneel.md new file mode 100644 index 00000000000..e8f10eee4d1 --- /dev/null +++ b/.changeset/twelve-jeans-kneel.md @@ -0,0 +1,5 @@ +--- +"@primer/components": patch +--- + +Perform ActionMenu actions after overlay has closed. This allows the action to move focus if so desired, without the ActionMenu focus trap preventing focus from moving away. diff --git a/src/ActionMenu.tsx b/src/ActionMenu.tsx index 7e99c403e41..8985e00a796 100644 --- a/src/ActionMenu.tsx +++ b/src/ActionMenu.tsx @@ -2,7 +2,7 @@ import {GroupedListProps, List, ListPropsBase} from './ActionList/List' import {Item, ItemProps} from './ActionList/Item' import {Divider} from './ActionList/Divider' import Button, {ButtonProps} from './Button' -import React, {useCallback, useState} from 'react' +import React, {useCallback, useEffect, useRef, useState} from 'react' import {AnchoredOverlay} from './AnchoredOverlay' export interface ActionMenuProps extends Partial>, ListPropsBase { @@ -26,6 +26,7 @@ const ActionMenuBase = ({ const [open, setOpen] = useState(false) const onOpen = useCallback(() => setOpen(true), []) const onClose = useCallback(() => setOpen(false), []) + const pendingActionRef = useRef<() => unknown>() const renderMenuAnchor = useCallback( >(props: T) => { @@ -45,7 +46,7 @@ const ActionMenuBase = ({ role: 'menuitem', onAction: (props, event) => { const actionCallback = itemOnAction ?? onAction - actionCallback?.(props as ItemProps, event) + pendingActionRef.current = () => actionCallback?.(props as ItemProps, event) onClose() } }) @@ -53,6 +54,15 @@ const ActionMenuBase = ({ [onAction, onClose, renderItem] ) + useEffect(() => { + // Wait until menu has re-rendered in a closed state before triggering action. + // This is needed in scenarios where the action will move focus, which would otherwise be captured by focus trap + if (!open && pendingActionRef.current) { + pendingActionRef.current() + pendingActionRef.current = undefined + } + }, [open]) + return (