Skip to content

Commit 732f960

Browse files
authored
Merge pull request Expensify#65255 from software-mansion-labs/feat/reanimated-year-picker-modal
Migrate YearPickerModal & more to use Reanimated modal
2 parents 04a89d4 + 755a03b commit 732f960

15 files changed

Lines changed: 114 additions & 60 deletions

File tree

src/components/CountryPicker/CountrySelectorModal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ function CountrySelectorModal({isVisible, currentCountry, onCountrySelected, onC
6666
hideModalContentWhileAnimating
6767
useNativeDriver
6868
onBackdropPress={onBackdropPress}
69+
shouldUseReanimatedModal
70+
backdropOpacity={0}
6971
>
7072
<ScreenWrapper
7173
style={[styles.pb0]}

src/components/CountryPicker/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React, {useState} from 'react';
22
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
33
import useLocalize from '@hooks/useLocalize';
4-
import Navigation from '@libs/Navigation/Navigation';
54
import type {Option} from '@libs/searchOptions';
65
import CONST from '@src/CONST';
76
import type {TranslationPaths} from '@src/languages/types';
@@ -47,7 +46,7 @@ function CountryPicker({value, errorText, onInputChange = () => {}}: CountryPick
4746
onCountrySelected={updateInput}
4847
onClose={hidePickerModal}
4948
label={translate('common.country')}
50-
onBackdropPress={Navigation.dismissModal}
49+
onBackdropPress={hidePickerModal}
5150
/>
5251
</>
5352
);

src/components/CurrencyPicker.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ function CurrencyPicker({
109109
shouldUseModalPaddingStyle={false}
110110
shouldHandleNavigationBack
111111
enableEdgeToEdgeBottomSafeAreaPadding
112+
shouldUseReanimatedModal
112113
>
113114
<ScreenWrapper
114115
style={[styles.pb0]}

src/components/DatePicker/CalendarPicker/YearPickerModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function YearPickerModal({isVisible, years, currentYear = new Date().getFullYear
5858
shouldUseCustomBackdrop
5959
onBackdropPress={onClose}
6060
enableEdgeToEdgeBottomSafeAreaPadding
61+
shouldUseReanimatedModal
6162
>
6263
<ScreenWrapper
6364
style={[styles.pb0]}

src/components/DatePicker/CalendarPicker/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ function CalendarPicker({
8686
);
8787

8888
const onYearSelected = (year: number) => {
89-
setIsYearPickerVisible(false);
9089
setCurrentDateView((prev) => {
9190
const newCurrentDateView = setYear(new Date(prev), year);
9291
setYears((prevYears) =>
@@ -97,6 +96,7 @@ function CalendarPicker({
9796
);
9897
return newCurrentDateView;
9998
});
99+
requestAnimationFrame(() => setIsYearPickerVisible(false));
100100
};
101101

102102
/**

src/components/Modal/BaseModal.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import ComposerFocusManager from '@libs/ComposerFocusManager';
2020
import NarrowPaneContext from '@libs/Navigation/AppNavigator/Navigators/NarrowPaneContext';
2121
import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay';
2222
import Navigation from '@libs/Navigation/Navigation';
23-
import variables from '@styles/variables';
2423
import {areAllModalsHidden, closeTop, onModalDidClose, setCloseModal, setModalVisibility, willAlertModalBecomeVisible} from '@userActions/Modal';
2524
import CONST from '@src/CONST';
2625
import ModalContent from './ModalContent';
@@ -93,6 +92,8 @@ function BaseModal(
9392
disableAnimationIn = false,
9493
enableEdgeToEdgeBottomSafeAreaPadding,
9594
shouldApplySidePanelOffset = type === CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED,
95+
hasBackdrop,
96+
backdropOpacity,
9697
shouldUseReanimatedModal = false,
9798
}: BaseModalProps,
9899
ref: React.ForwardedRef<View>,
@@ -226,8 +227,9 @@ function BaseModal(
226227
innerContainerStyle,
227228
outerStyle,
228229
shouldUseModalPaddingStyle,
230+
shouldUseReanimatedModal,
229231
),
230-
[StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle, shouldUseModalPaddingStyle],
232+
[StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle, shouldUseModalPaddingStyle, shouldUseReanimatedModal],
231233
);
232234

233235
const modalPaddingStyles = useMemo(() => {
@@ -318,9 +320,9 @@ function BaseModal(
318320
swipeThreshold={swipeThreshold}
319321
isVisible={isVisible}
320322
backdropColor={theme.overlay}
321-
backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : variables.overlayOpacity}
323+
backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : backdropOpacity}
322324
backdropTransitionOutTiming={0}
323-
hasBackdrop={fullscreen}
325+
hasBackdrop={hasBackdrop ?? fullscreen}
324326
coverScreen={fullscreen}
325327
style={[modalStyle, sidePanelStyle]}
326328
deviceHeight={windowHeight}

src/components/Modal/ReanimatedModal/Backdrop/index.web.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,20 @@ function Backdrop({
2222
const {translate} = useLocalize();
2323

2424
const Entering = useMemo(() => {
25+
if (!backdropOpacity) {
26+
return;
27+
}
2528
const FadeIn = new Keyframe(getModalInAnimation('fadeIn'));
26-
2729
return FadeIn.duration(animationInTiming);
28-
}, [animationInTiming]);
30+
}, [animationInTiming, backdropOpacity]);
2931

3032
const Exiting = useMemo(() => {
33+
if (!backdropOpacity) {
34+
return;
35+
}
3136
const FadeOut = new Keyframe(getModalOutAnimation('fadeOut'));
32-
3337
return FadeOut.duration(animationOutTiming);
34-
}, [animationOutTiming]);
38+
}, [animationOutTiming, backdropOpacity]);
3539

3640
const backdropStyle = useMemo(
3741
() => ({
@@ -61,14 +65,18 @@ function Backdrop({
6165
}
6266
return (
6367
isBackdropVisible && (
64-
<Animated.View
65-
entering={Entering}
66-
exiting={Exiting}
68+
<View
6769
style={[styles.userSelectNone]}
6870
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
6971
>
70-
<View style={[styles.modalBackdrop, backdropStyle, style]}>{!!customBackdrop && customBackdrop}</View>
71-
</Animated.View>
72+
<Animated.View
73+
entering={Entering}
74+
exiting={Exiting}
75+
style={[styles.modalBackdrop, backdropStyle, style]}
76+
>
77+
{!!customBackdrop && customBackdrop}
78+
</Animated.View>
79+
</View>
7280
)
7381
);
7482
}

src/components/Modal/ReanimatedModal/Container/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import GestureHandler from './GestureHandler';
1010

1111
function Container({
1212
style,
13-
animationInTiming = 300,
14-
animationOutTiming = 300,
13+
animationInTiming = CONST.MODAL.ANIMATION_TIMING.DEFAULT_IN,
14+
animationOutTiming = CONST.MODAL.ANIMATION_TIMING.DEFAULT_OUT,
1515
onCloseCallBack,
1616
onOpenCallBack,
1717
animationIn,

src/components/Modal/ReanimatedModal/Container/index.web.tsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,26 @@ import React, {useEffect, useMemo, useRef} from 'react';
22
import Animated, {Easing, Keyframe, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
33
import type ReanimatedModalProps from '@components/Modal/ReanimatedModal/types';
44
import type {ContainerProps} from '@components/Modal/ReanimatedModal/types';
5+
import {getModalInAnimationStyle, getModalOutAnimation} from '@components/Modal/ReanimatedModal/utils';
56
import useThemeStyles from '@hooks/useThemeStyles';
7+
import CONST from '@src/CONST';
68

79
const easing = Easing.bezier(0.76, 0.0, 0.24, 1.0).factory();
810

9-
function Container({style, animationInTiming = 300, animationOutTiming = 300, onOpenCallBack, onCloseCallBack, ...props}: ReanimatedModalProps & ContainerProps) {
11+
function Container({
12+
style,
13+
animationIn,
14+
animationOut,
15+
animationInTiming = CONST.MODAL.ANIMATION_TIMING.DEFAULT_IN,
16+
animationOutTiming = CONST.MODAL.ANIMATION_TIMING.DEFAULT_OUT,
17+
onOpenCallBack,
18+
onCloseCallBack,
19+
type,
20+
...props
21+
}: ReanimatedModalProps & ContainerProps) {
1022
const styles = useThemeStyles();
1123
const onCloseCallbackRef = useRef(onCloseCallBack);
12-
const opacity = useSharedValue(0);
24+
const initProgress = useSharedValue(0);
1325
const isInitiated = useSharedValue(false);
1426

1527
useEffect(() => {
@@ -21,27 +33,22 @@ function Container({style, animationInTiming = 300, animationOutTiming = 300, on
2133
return;
2234
}
2335
isInitiated.set(true);
24-
opacity.set(withTiming(1, {duration: animationInTiming, easing}, onOpenCallBack));
25-
}, [animationInTiming, onOpenCallBack, opacity, isInitiated]);
36+
initProgress.set(withTiming(1, {duration: animationInTiming, easing}, onOpenCallBack));
37+
}, [animationInTiming, onOpenCallBack, initProgress, isInitiated]);
2638

27-
const animatedStyles = useAnimatedStyle(() => ({opacity: opacity.get()}), [opacity]);
39+
// instead of an entering transition since keyframe animations break keyboard on mWeb Chrome (#62799)
40+
const animatedStyles = useAnimatedStyle(() => getModalInAnimationStyle(animationIn)(initProgress.get()), [initProgress]);
2841

2942
const Exiting = useMemo(() => {
30-
const FadeOut = new Keyframe({
31-
from: {opacity: 1},
32-
to: {
33-
opacity: 0,
34-
easing,
35-
},
36-
});
43+
const AnimationOut = new Keyframe(getModalOutAnimation(animationOut));
3744

3845
// eslint-disable-next-line react-compiler/react-compiler
39-
return FadeOut.duration(animationOutTiming).withCallback(() => onCloseCallbackRef.current());
40-
}, [animationOutTiming]);
46+
return AnimationOut.duration(animationOutTiming).withCallback(() => onCloseCallbackRef.current());
47+
}, [animationOutTiming, animationOut]);
4148

4249
return (
4350
<Animated.View
44-
style={[style, styles.modalContainer, styles.modalAnimatedContainer, animatedStyles]}
51+
style={[style, styles.modalContainer, type !== CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED && styles.modalAnimatedContainer, animatedStyles, {zIndex: 1}]}
4552
exiting={Exiting}
4653
// eslint-disable-next-line react/jsx-props-no-spreading
4754
{...props}

src/components/Modal/ReanimatedModal/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {LayoutAnimationConfig} from 'react-native-reanimated';
66
import KeyboardAvoidingView from '@components/KeyboardAvoidingView';
77
import useThemeStyles from '@hooks/useThemeStyles';
88
import getPlatform from '@libs/getPlatform';
9+
import variables from '@styles/variables';
910
import CONST from '@src/CONST';
1011
import Backdrop from './Backdrop';
1112
import Container from './Container';
@@ -15,16 +16,16 @@ import type {AnimationInType, AnimationOutType} from './types';
1516
function ReanimatedModal({
1617
testID,
1718
animationInDelay,
18-
animationInTiming = 300,
19-
animationOutTiming = 300,
19+
animationInTiming = CONST.MODAL.ANIMATION_TIMING.DEFAULT_IN,
20+
animationOutTiming = CONST.MODAL.ANIMATION_TIMING.DEFAULT_OUT,
2021
animationIn = 'fadeIn',
2122
animationOut = 'fadeOut',
2223
avoidKeyboard = false,
2324
coverScreen = true,
2425
children,
2526
hasBackdrop = true,
2627
backdropColor = 'black',
27-
backdropOpacity = 0.72,
28+
backdropOpacity = variables.overlayOpacity,
2829
customBackdrop = null,
2930
isVisible = false,
3031
onModalWillShow = noop,

0 commit comments

Comments
 (0)