From 9ce4b3275ebc80cd83eac5a52c142df746fc76ef Mon Sep 17 00:00:00 2001 From: Brad Stiff Date: Thu, 15 Jan 2026 14:09:53 -0700 Subject: [PATCH 1/3] (fix): defer unmounting Portal until hide animation finishes --- src/components/Menu/Menu.tsx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 185b0a3a53..956b362562 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -395,7 +395,7 @@ const Menu = ({ return; } - if (!display && prevRendered.current) { + if (!display) { hide(); } @@ -430,15 +430,23 @@ const Menu = ({ if (prevVisible.current !== visible) { prevVisible.current = visible; - if (visible !== rendered) { - setRendered(visible); + if (visible) { + if (!rendered) { + // Mount the Portal before attempting to show. + setRendered(true); + } + } else { + // Keep the Portal mounted so the hide animation can finish. + updateVisibility(false); } } - }, [visible, rendered]); + }, [visible, rendered, updateVisibility]); React.useEffect(() => { - updateVisibility(rendered); - }, [rendered, updateVisibility]); + if (rendered && visible) { + updateVisibility(true); + } + }, [rendered, visible, updateVisibility]); // I don't know why but on Android measure function is wrong by 24 const additionalVerticalValue = Platform.select({ @@ -639,6 +647,7 @@ const Menu = ({ accessibilityLabel={overlayAccessibilityLabel} accessibilityRole="button" onPress={onDismiss} + pointerEvents={visible ? 'auto' : 'none'} style={styles.pressableOverlay} /> Date: Thu, 15 Jan 2026 16:03:17 -0700 Subject: [PATCH 2/3] fix(menu): wait a tick before ensuring the component --- src/components/__tests__/Menu.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/__tests__/Menu.test.tsx b/src/components/__tests__/Menu.test.tsx index 68e73661e6..29397d1262 100644 --- a/src/components/__tests__/Menu.test.tsx +++ b/src/components/__tests__/Menu.test.tsx @@ -131,6 +131,8 @@ it('uses the default anchorPosition of top', async () => { // itself. await act(async () => { screen.update(makeMenu(true)); + // Menu waits a tick for Portal refs to be up-to-date. + await Promise.resolve(); }); await waitFor(() => { @@ -182,6 +184,8 @@ it('respects anchorPosition bottom', async () => { await act(async () => { screen.update(makeMenu(true)); + // Menu waits a tick for Portal refs to be up-to-date. + await Promise.resolve(); }); await waitFor(() => { From 824eba6d022730202722dfd0af90b38d8cb8e721 Mon Sep 17 00:00:00 2001 From: Brad Stiff Date: Thu, 15 Jan 2026 16:03:41 -0700 Subject: [PATCH 3/3] fix(menu): add pointerEvents to snapshot styles --- src/components/__tests__/__snapshots__/Menu.test.tsx.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/__tests__/__snapshots__/Menu.test.tsx.snap b/src/components/__tests__/__snapshots__/Menu.test.tsx.snap index 801552dab1..fc3ac61473 100644 --- a/src/components/__tests__/__snapshots__/Menu.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Menu.test.tsx.snap @@ -209,6 +209,7 @@ exports[`renders menu with content styles 1`] = ` onResponderTerminate={[Function]} onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} + pointerEvents="auto" style={ { "bottom": 0, @@ -933,6 +934,7 @@ exports[`renders visible menu 1`] = ` onResponderTerminate={[Function]} onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} + pointerEvents="auto" style={ { "bottom": 0,