From 4a562864fed2ed2d51880d3d90ad7a6c1a91dcbe Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Thu, 25 Sep 2025 09:50:30 -0500 Subject: [PATCH] fix(Menu): show checkboxes for selected items with ContextualHelpTrigger isUnavailable={false} (#8921) * fix selection state in Menu items with ContextualHelpTrigger and isUnavailable={false} * cleanup test --- .../@react-spectrum/menu/src/MenuItem.tsx | 6 +++- .../menu/stories/MenuTrigger.stories.tsx | 35 ++++++++++++++++++ .../menu/test/MenuTrigger.test.js | 36 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/@react-spectrum/menu/src/MenuItem.tsx b/packages/@react-spectrum/menu/src/MenuItem.tsx index 7277dc253cc..519e514787d 100644 --- a/packages/@react-spectrum/menu/src/MenuItem.tsx +++ b/packages/@react-spectrum/menu/src/MenuItem.tsx @@ -64,7 +64,11 @@ export function MenuItem(props: MenuItemProps): JSX.Element { } let isDisabled = state.disabledKeys.has(key); - let isSelectable = !isSubmenuTrigger && state.selectionManager.selectionMode !== 'none'; + let isContextualHelpTrigger = isSubmenuTrigger && isUnavailable !== undefined; + let isSelectable = ( + (isContextualHelpTrigger ? !isUnavailable : !isSubmenuTrigger) && + state.selectionManager.selectionMode !== 'none' + ); let isSelected = isSelectable && state.selectionManager.isSelected(key); let itemref = useRef(null); let ref = useObjectRef(useMemo(() => mergeRefs(itemref, triggerRef), [itemref, triggerRef])); diff --git a/packages/@react-spectrum/menu/stories/MenuTrigger.stories.tsx b/packages/@react-spectrum/menu/stories/MenuTrigger.stories.tsx index 29f611fcc7a..7233a2f7a29 100644 --- a/packages/@react-spectrum/menu/stories/MenuTrigger.stories.tsx +++ b/packages/@react-spectrum/menu/stories/MenuTrigger.stories.tsx @@ -786,6 +786,41 @@ export let MenuItemUnavailable: StoryObj = { ) }; +export let MenuItemUnavailableWithSelection: StoryObj = { + render: () => render( + + One + + Two + + hello + Is it me you're looking for? + + + + Two point five + + hello + Is it me you're looking for? + + + Three + + + Four + Shut the door + + + hello + Is it me you're looking for? +
Learn more
+
+
+ Five +
+ ) +}; + export let MenuItemUnavailableDynamic: StoryObj = { render: () => render( diff --git a/packages/@react-spectrum/menu/test/MenuTrigger.test.js b/packages/@react-spectrum/menu/test/MenuTrigger.test.js index 567ff686c51..8714c0765bf 100644 --- a/packages/@react-spectrum/menu/test/MenuTrigger.test.js +++ b/packages/@react-spectrum/menu/test/MenuTrigger.test.js @@ -617,6 +617,42 @@ describe('MenuTrigger', function () { expect(tree.queryByRole('dialog')).toBeNull(); }); + it('shows selection state for available ContextualHelpTrigger items', async function () { + render( + + + Menu + + One + + + Hello + Is it me you're looking for? + + + Lionel Richie says: + I can see it in your eyes + + + + + + ); + + act(() => {jest.runAllTimers();}); + + let menu = screen.getByRole('menu'); + let availableItem = within(menu).getByRole('menuitemradio', {name: 'Hello'}); + + expect(availableItem).toBeVisible(); + expect(availableItem).toHaveAttribute('aria-checked', 'false'); + fireEvent.click(availableItem); + act(() => {jest.runAllTimers();}); + + expect(availableItem).toHaveAttribute('aria-checked', 'true'); + expect(availableItem).not.toHaveAttribute('aria-haspopup', 'dialog'); + }); + it('can open a sub dialog with keyboard', async function () { renderTree(); let menu = await openMenu();