From 3223b42f11edef8afdc6f398d4afd51b1bf8c55a Mon Sep 17 00:00:00 2001 From: Chuck Dries Date: Fri, 19 Jun 2026 16:24:47 -0700 Subject: [PATCH] Animate entire MFA panel out on finishClose --- ...ultifactorAuthenticationModalNavigator.tsx | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx index 33021747593c..f097769760df 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/MultifactorAuthenticationModalNavigator.tsx @@ -1,8 +1,9 @@ import {BaseNavigationContainer, NavigationIndependentTree} from '@react-navigation/core'; import type {StackCardInterpolationProps} from '@react-navigation/stack'; -import React, {useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; import {StyleSheet, View} from 'react-native'; -import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import Animated, {cancelAnimation, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {scheduleOnRN} from 'react-native-worklets'; import {DefaultCancelConfirmModal} from '@components/MultifactorAuthentication/components/Modals'; import {useMultifactorAuthentication, useMultifactorAuthenticationActions, useMultifactorAuthenticationState} from '@components/MultifactorAuthentication/Context'; import type {MultifactorAuthenticationModalNavigatorInternalParamList} from '@components/MultifactorAuthentication/mfaNavigation'; @@ -15,8 +16,8 @@ import useSidePanelState from '@hooks/useSidePanelState'; import useTheme from '@hooks/useTheme'; import useThemePreference from '@hooks/useThemePreference'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import getNavigationBaseTheme from '@libs/Navigation/getNavigationBaseTheme'; -import Navigation from '@libs/Navigation/Navigation'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation'; import Presentation from '@libs/Navigation/PlatformStackNavigation/navigationOptions/presentation'; @@ -87,6 +88,9 @@ function MultifactorAuthenticationModalNavigator() { const [phase, setPhase] = useState(isModalOpen ? 'open' : 'closed'); const backdropProgress = useSharedValue(0); + const panelTranslateX = useSharedValue(0); + const {windowWidth} = useWindowDimensions(); + const panelWidth = shouldUseNarrowLayout ? windowWidth : variables.sideBarWidth; const modalCardStyleInterpolator = useModalCardStyleInterpolator(); const CancelConfirmModal = scenario?.modals.cancelConfirmation ?? DefaultCancelConfirmModal; @@ -108,30 +112,44 @@ function MultifactorAuthenticationModalNavigator() { }, }; + const finishClose = useCallback(() => { + resetMfaNavigation(); + setPhase('closed'); + dispatch({type: 'RESET'}); + }, [dispatch]); + useEffect(() => { if (phase === 'open') { + panelTranslateX.set(0); backdropProgress.set(withTiming(1, {duration: CONST.ANIMATED_TRANSITION})); return; } if (phase !== 'closing') { return; } - if (mfaNavigationRef.isReady() && mfaNavigationRef.canGoBack()) { - mfaNavigationRef.goBack(); - } + // Slide the whole overlay out as one unit and fade the backdrop in parallel. Animating a single + // screen (e.g. via goBack) would reveal screens still on the stack beneath the outcome screen + // before the overlay unmounts. backdropProgress.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION})); - const handle = Navigation.runAfterUpcomingTransition(() => { - resetMfaNavigation(); - setPhase('closed'); - dispatch({type: 'RESET'}); - }); - return () => handle.cancel(); - }, [phase, backdropProgress, dispatch]); + panelTranslateX.set( + withTiming(panelWidth, {duration: CONST.ANIMATED_TRANSITION}, (finished) => { + if (!finished) { + return; + } + scheduleOnRN(finishClose); + }), + ); + return () => cancelAnimation(panelTranslateX); + }, [phase, backdropProgress, panelTranslateX, panelWidth, finishClose]); const backdropAnimatedStyle = useAnimatedStyle(() => ({ opacity: backdropProgress.get() * variables.overlayOpacity, })); + const panelAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{translateX: panelTranslateX.get()}], + })); + if (phase === 'closed') { return null; } @@ -153,7 +171,9 @@ function MultifactorAuthenticationModalNavigator() { /> )} - + {isStackReadyToMount && ( )} - +