diff --git a/.changeset/fix-menu-escape-focus.md b/.changeset/fix-menu-escape-focus.md
new file mode 100644
index 00000000000..46d1b555e3d
--- /dev/null
+++ b/.changeset/fix-menu-escape-focus.md
@@ -0,0 +1,9 @@
+---
+'@kaizen/components': patch
+---
+
+Fix Menu dropdown not returning focus to trigger button when pressing Escape
+
+Previously, pressing Escape to close a Menu would lose focus because the focus restoration was targeting the positioning wrapper element instead of the actual button. This affected components like the Export button in TitleBlock.
+
+No changes required for consumers - this is a bug fix.
diff --git a/packages/components/src/MenuV1/Menu.spec.tsx b/packages/components/src/MenuV1/Menu.spec.tsx
index f35513bb3b9..16389231799 100644
--- a/packages/components/src/MenuV1/Menu.spec.tsx
+++ b/packages/components/src/MenuV1/Menu.spec.tsx
@@ -60,4 +60,26 @@ describe('Dropdown', () => {
expect(onMouseDown).toBeCalled()
})
})
+
+ it('returns focus to the button when pressing Escape', async () => {
+ render(
+
}>
+ Item
+ ,
+ )
+
+ const button = screen.getByRole('button', { name: 'Button' })
+ await user.click(button)
+
+ await waitFor(() => {
+ expect(screen.getByText('Item')).toBeVisible()
+ })
+
+ await user.keyboard('{Escape}')
+
+ await waitFor(() => {
+ expect(screen.queryByText('Item')).not.toBeInTheDocument()
+ expect(button).toHaveFocus()
+ })
+ })
})
diff --git a/packages/components/src/MenuV1/Menu.tsx b/packages/components/src/MenuV1/Menu.tsx
index 0f602b69269..0c64b000021 100644
--- a/packages/components/src/MenuV1/Menu.tsx
+++ b/packages/components/src/MenuV1/Menu.tsx
@@ -1,10 +1,24 @@
-import React, { useState } from 'react'
+import React, { useState, type Ref } from 'react'
import type { ButtonProps } from '~components/ButtonV1'
+import type { ButtonRef } from '~components/ButtonV1/GenericButton'
import { StatelessMenu, type StatelessMenuProps } from './subcomponents/StatelessMenu'
+const mergeRefs =
+ (...refs: (Ref | undefined)[]) =>
+ (value: T | null): void => {
+ refs.forEach((ref) => {
+ if (typeof ref === 'function') {
+ ref(value)
+ } else if (ref && typeof ref === 'object') {
+ ;(ref as React.MutableRefObject).current = value
+ }
+ })
+ }
+
type ButtonPropsWithOptionalAria = ButtonProps & {
'aria-haspopup'?: boolean
'aria-expanded'?: boolean
+ 'ref'?: Ref
}
export type MenuProps = Omit<
@@ -61,6 +75,10 @@ export const Menu = ({ button, menuVisible = false, ...rest }: MenuProps): JSX.E
props.onMouseDown(e)
button.props.onMouseDown?.(e)
},
+ ref: mergeRefs(
+ props.ref as Ref,
+ button.props.ref as Ref | undefined,
+ ),
})
}
/>
diff --git a/packages/components/src/MenuV1/subcomponents/MenuDropdown/MenuDropdown.tsx b/packages/components/src/MenuV1/subcomponents/MenuDropdown/MenuDropdown.tsx
index 198c6e218b0..f6f9a05d4d2 100644
--- a/packages/components/src/MenuV1/subcomponents/MenuDropdown/MenuDropdown.tsx
+++ b/packages/components/src/MenuV1/subcomponents/MenuDropdown/MenuDropdown.tsx
@@ -18,11 +18,13 @@ export type MenuDropdownProps = {
autoHide?: 'on' | 'outside-click-only' | 'off'
children: React.ReactNode
referenceElement: HTMLElement | null
+ buttonRef?: React.RefObject<{ focus: () => void } | null>
}
export const MenuDropdown = ({
children,
referenceElement,
+ buttonRef,
id,
hideMenuDropdown,
autoHide = 'on',
@@ -104,7 +106,11 @@ export const MenuDropdown = ({
shards={referenceElement ? [referenceElement] : undefined}
onEscapeKey={hideMenuDropdown}
returnFocus={() => {
- referenceElement?.focus()
+ if (buttonRef?.current) {
+ buttonRef.current.focus()
+ } else {
+ referenceElement?.focus()
+ }
return false
}}
>
diff --git a/packages/components/src/MenuV1/subcomponents/StatelessMenu/StatelessMenu.tsx b/packages/components/src/MenuV1/subcomponents/StatelessMenu/StatelessMenu.tsx
index fe060d14fd7..51b4d5d48af 100644
--- a/packages/components/src/MenuV1/subcomponents/StatelessMenu/StatelessMenu.tsx
+++ b/packages/components/src/MenuV1/subcomponents/StatelessMenu/StatelessMenu.tsx
@@ -43,6 +43,7 @@ export type StatelessMenuProps = {
'onClick': (e: any) => void
'onMouseDown': (e: any) => void
'aria-expanded': boolean
+ 'ref': React.RefObject<{ focus: () => void } | null>
}) => React.ReactElement
'onClick'?: (event: SyntheticEvent) => void
}
@@ -62,6 +63,7 @@ export const StatelessMenu = ({
onClick,
}: StatelessMenuProps): JSX.Element => {
const [referenceElement, setReferenceElement] = useState(null)
+ const buttonRef = useRef<{ focus: () => void } | null>(null)
const portalSelectorElementRef = useRef(null)
const menuButton = renderButton({
@@ -72,6 +74,7 @@ export const StatelessMenu = ({
},
'onMouseDown': (e: React.MouseEvent) => e.preventDefault(),
'aria-expanded': isMenuVisible,
+ 'ref': buttonRef,
})
useEffect(() => {
@@ -90,6 +93,7 @@ export const StatelessMenu = ({
const menu = isMenuVisible ? (