diff --git a/jest/setup.ts b/jest/setup.ts index 38910e1acf74..90821bba5b8c 100644 --- a/jest/setup.ts +++ b/jest/setup.ts @@ -78,6 +78,7 @@ jest.mock('react-native-reanimated', () => ({ ...jest.requireActual('react-native-reanimated/mock'), createAnimatedPropAdapter: jest.fn, useReducedMotion: jest.fn, + LayoutAnimationConfig: jest.fn, })); jest.mock('react-native-keyboard-controller', () => require('react-native-keyboard-controller/jest')); diff --git a/src/components/EmojiPicker/EmojiPicker.tsx b/src/components/EmojiPicker/EmojiPicker.tsx index 79af5bc0a4f2..51ae120d80bf 100644 --- a/src/components/EmojiPicker/EmojiPicker.tsx +++ b/src/components/EmojiPicker/EmojiPicker.tsx @@ -12,9 +12,9 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import type {AnchorOrigin, EmojiPickerRef, EmojiPopoverAnchor, OnEmojiSelected, OnModalHideValue, OnWillShowPicker} from '@libs/actions/EmojiPickerAction'; -import * as Browser from '@libs/Browser'; +import {isMobileChrome} from '@libs/Browser'; import calculateAnchorPosition from '@libs/calculateAnchorPosition'; -import * as Modal from '@userActions/Modal'; +import {close} from '@userActions/Modal'; import CONST from '@src/CONST'; import EmojiPickerMenu from './EmojiPickerMenu'; @@ -92,7 +92,7 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { - Modal.close(() => { + close(() => { onWillShow?.(); setIsEmojiPickerVisible(true); setEmojiPopoverAnchorPosition({ @@ -190,19 +190,14 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 6ead97c563c6..2b0b9371b248 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -94,7 +94,7 @@ function BaseModal( const sidePaneStyle = shouldApplySidePaneOffset && !isSmallScreenWidth ? {paddingRight: sidePaneOffset.current} : undefined; const keyboardStateContextValue = useKeyboardState(); - const safeAreaInsets = useSafeAreaInsets(); + const insets = useSafeAreaInsets(); const isVisibleRef = useRef(isVisible); const hideModalCallbackRef = useRef<(callHideCallback: boolean) => void>(); @@ -211,34 +211,30 @@ function BaseModal( [StyleUtils, type, windowWidth, windowHeight, isSmallScreenWidth, popoverAnchorPosition, innerContainerStyle, outerStyle], ); - const { - paddingTop: safeAreaPaddingTop, - paddingBottom: safeAreaPaddingBottom, - paddingLeft: safeAreaPaddingLeft, - paddingRight: safeAreaPaddingRight, - } = StyleUtils.getPlatformSafeAreaPadding(safeAreaInsets); - - const modalPaddingStyles = shouldUseModalPaddingStyle - ? StyleUtils.getModalPaddingStyles({ - safeAreaPaddingTop, - safeAreaPaddingBottom, - safeAreaPaddingLeft, - safeAreaPaddingRight, - shouldAddBottomSafeAreaMargin, - shouldAddTopSafeAreaMargin, - // enableEdgeToEdgeBottomSafeAreaPadding is used as a temporary solution to disable safe area bottom spacing on modals, to allow edge-to-edge content - shouldAddBottomSafeAreaPadding: !enableEdgeToEdgeBottomSafeAreaPadding && (!avoidKeyboard || !keyboardStateContextValue?.isKeyboardShown) && shouldAddBottomSafeAreaPadding, - shouldAddTopSafeAreaPadding, - modalContainerStyleMarginTop: modalContainerStyle.marginTop, - modalContainerStyleMarginBottom: modalContainerStyle.marginBottom, - modalContainerStylePaddingTop: modalContainerStyle.paddingTop, - modalContainerStylePaddingBottom: modalContainerStyle.paddingBottom, - insets: safeAreaInsets, - }) - : { - paddingLeft: safeAreaPaddingLeft ?? 0, - paddingRight: safeAreaPaddingRight ?? 0, - }; + const modalPaddingStyles = useMemo(() => { + const paddings = StyleUtils.getModalPaddingStyles({ + shouldAddBottomSafeAreaMargin, + shouldAddTopSafeAreaMargin, + // enableEdgeToEdgeBottomSafeAreaPadding is used as a temporary solution to disable safe area bottom spacing on modals, to allow edge-to-edge content + shouldAddBottomSafeAreaPadding: !enableEdgeToEdgeBottomSafeAreaPadding && (!avoidKeyboard || !keyboardStateContextValue.isKeyboardActive) && shouldAddBottomSafeAreaPadding, + shouldAddTopSafeAreaPadding, + modalContainerStyle, + insets, + }); + return shouldUseModalPaddingStyle ? paddings : {paddingLeft: paddings.paddingLeft, paddingRight: paddings.paddingRight}; + }, [ + StyleUtils, + avoidKeyboard, + enableEdgeToEdgeBottomSafeAreaPadding, + insets, + keyboardStateContextValue.isKeyboardActive, + modalContainerStyle, + shouldAddBottomSafeAreaMargin, + shouldAddBottomSafeAreaPadding, + shouldAddTopSafeAreaMargin, + shouldAddTopSafeAreaPadding, + shouldUseModalPaddingStyle, + ]); const modalContextValue = useMemo( () => ({ diff --git a/src/components/Modal/BottomDockedModal/index.tsx b/src/components/Modal/BottomDockedModal/index.tsx index 44eca06d2b5c..f1cf0ef559e0 100644 --- a/src/components/Modal/BottomDockedModal/index.tsx +++ b/src/components/Modal/BottomDockedModal/index.tsx @@ -130,7 +130,7 @@ function BottomDockedModal({ width: deviceWidthProp ?? deviceWidth, height: deviceHeightProp ?? deviceHeight, backgroundColor: backdropColor, - opacity: getPlatform() === CONST.PLATFORM.WEB ? backdropOpacity : 1, + ...(getPlatform() === CONST.PLATFORM.WEB ? {opacity: backdropOpacity} : {}), }; }, [deviceHeightProp, deviceWidthProp, deviceWidth, deviceHeight, backdropColor, backdropOpacity]); @@ -215,10 +215,9 @@ function BottomDockedModal({ {...props} > {isVisibleState && hasBackdrop && backdropView} - {avoidKeyboard ? ( diff --git a/src/components/PopoverWithoutOverlay/index.tsx b/src/components/PopoverWithoutOverlay/index.tsx index 5dfaddcab2df..b4c65e221c38 100644 --- a/src/components/PopoverWithoutOverlay/index.tsx +++ b/src/components/PopoverWithoutOverlay/index.tsx @@ -72,46 +72,17 @@ function PopoverWithoutOverlay( // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isVisible]); - const { - paddingTop: safeAreaPaddingTop, - paddingBottom: safeAreaPaddingBottom, - paddingLeft: safeAreaPaddingLeft, - paddingRight: safeAreaPaddingRight, - } = useMemo(() => StyleUtils.getPlatformSafeAreaPadding(insets), [StyleUtils, insets]); - const modalPaddingStyles = useMemo( () => StyleUtils.getModalPaddingStyles({ - safeAreaPaddingTop, - safeAreaPaddingBottom, - safeAreaPaddingLeft, - safeAreaPaddingRight, shouldAddBottomSafeAreaMargin, shouldAddTopSafeAreaMargin, shouldAddBottomSafeAreaPadding, shouldAddTopSafeAreaPadding, - modalContainerStyleMarginTop: modalContainerStyle.marginTop, - modalContainerStyleMarginBottom: modalContainerStyle.marginBottom, - modalContainerStylePaddingTop: modalContainerStyle.paddingTop, - modalContainerStylePaddingBottom: modalContainerStyle.paddingBottom, + modalContainerStyle, insets, }), - [ - StyleUtils, - insets, - modalContainerStyle.marginBottom, - modalContainerStyle.marginTop, - modalContainerStyle.paddingBottom, - modalContainerStyle.paddingTop, - safeAreaPaddingBottom, - safeAreaPaddingLeft, - safeAreaPaddingRight, - safeAreaPaddingTop, - shouldAddBottomSafeAreaMargin, - shouldAddBottomSafeAreaPadding, - shouldAddTopSafeAreaMargin, - shouldAddTopSafeAreaPadding, - ], + [StyleUtils, insets, modalContainerStyle, shouldAddBottomSafeAreaMargin, shouldAddBottomSafeAreaPadding, shouldAddTopSafeAreaMargin, shouldAddTopSafeAreaPadding], ); if (!isVisible) { diff --git a/src/components/withKeyboardState.tsx b/src/components/withKeyboardState.tsx index 15150485d499..a35321929ff9 100755 --- a/src/components/withKeyboardState.tsx +++ b/src/components/withKeyboardState.tsx @@ -10,14 +10,19 @@ type KeyboardStateContextValue = { /** Whether the keyboard is open */ isKeyboardShown: boolean; + /** Whether the keyboard is animating or shown */ + isKeyboardActive: boolean; + /** Height of the keyboard in pixels */ keyboardHeight: number; + /** Ref to check if the keyboard is animating */ isKeyboardAnimatingRef: MutableRefObject; }; const KeyboardStateContext = createContext({ isKeyboardShown: false, + isKeyboardActive: false, keyboardHeight: 0, isKeyboardAnimatingRef: {current: false}, }); @@ -26,6 +31,7 @@ function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null { const {bottom} = useSafeAreaInsets(); const [keyboardHeight, setKeyboardHeight] = useState(0); const isKeyboardAnimatingRef = useRef(false); + const [isKeyboardActive, setIsKeyboardActive] = useState(false); useEffect(() => { const keyboardDidShowListener = KeyboardEvents.addListener('keyboardDidShow', (e) => { @@ -34,10 +40,18 @@ function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null { const keyboardDidHideListener = KeyboardEvents.addListener('keyboardDidHide', () => { setKeyboardHeight(0); }); + const keyboardWillShowListener = KeyboardEvents.addListener('keyboardWillShow', () => { + setIsKeyboardActive(true); + }); + const keyboardWillHideListener = KeyboardEvents.addListener('keyboardWillHide', () => { + setIsKeyboardActive(false); + }); return () => { keyboardDidShowListener.remove(); keyboardDidHideListener.remove(); + keyboardWillShowListener.remove(); + keyboardWillHideListener.remove(); }; }, [bottom]); @@ -66,8 +80,9 @@ function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null { keyboardHeight, isKeyboardShown: keyboardHeight !== 0, isKeyboardAnimatingRef, + isKeyboardActive, }), - [keyboardHeight], + [isKeyboardActive, keyboardHeight], ); return {children}; } diff --git a/src/styles/index.ts b/src/styles/index.ts index 5cdeae416613..7d5c04fa8f2c 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1917,7 +1917,7 @@ const styles = (theme: ThemeColors) => popoverInnerContainer: { paddingTop: 0, // adjusting this because the mobile modal adds additional padding that we don't need for our layout - maxHeight: '95%', + backgroundColor: theme.modalBackground, }, menuItemTextContainer: { diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 1d8faeb41cc7..fa8e1d9aa1eb 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -589,14 +589,7 @@ type ModalPaddingStylesParams = { shouldAddTopSafeAreaMargin: boolean; shouldAddBottomSafeAreaPadding: boolean; shouldAddTopSafeAreaPadding: boolean; - safeAreaPaddingTop: number; - safeAreaPaddingBottom: number; - safeAreaPaddingLeft: number; - safeAreaPaddingRight: number; - modalContainerStyleMarginTop: MarginPaddingValue; - modalContainerStyleMarginBottom: MarginPaddingValue; - modalContainerStylePaddingTop: MarginPaddingValue; - modalContainerStylePaddingBottom: MarginPaddingValue; + modalContainerStyle: ViewStyle; insets: EdgeInsets; }; @@ -605,24 +598,19 @@ function getModalPaddingStyles({ shouldAddTopSafeAreaMargin, shouldAddBottomSafeAreaPadding, shouldAddTopSafeAreaPadding, - safeAreaPaddingTop, - safeAreaPaddingBottom, - safeAreaPaddingLeft, - safeAreaPaddingRight, - modalContainerStyleMarginTop, - modalContainerStyleMarginBottom, - modalContainerStylePaddingTop, - modalContainerStylePaddingBottom, + modalContainerStyle, insets, }: ModalPaddingStylesParams): ViewStyle { + const {paddingTop: safeAreaPaddingTop, paddingBottom: safeAreaPaddingBottom, paddingLeft: safeAreaPaddingLeft, paddingRight: safeAreaPaddingRight} = getPlatformSafeAreaPadding(insets); + // use fallback value for safeAreaPaddingBottom to keep padding bottom consistent with padding top. // More info: issue #17376 - const safeAreaPaddingBottomWithFallback = insets.bottom === 0 && typeof modalContainerStylePaddingTop === 'number' ? modalContainerStylePaddingTop ?? 0 : safeAreaPaddingBottom; + const safeAreaPaddingBottomWithFallback = insets.bottom === 0 && typeof modalContainerStyle.paddingTop === 'number' ? modalContainerStyle.paddingTop ?? 0 : safeAreaPaddingBottom; return { - marginTop: getCombinedSpacing(modalContainerStyleMarginTop, safeAreaPaddingTop, shouldAddTopSafeAreaMargin), - marginBottom: getCombinedSpacing(modalContainerStyleMarginBottom, safeAreaPaddingBottomWithFallback, shouldAddBottomSafeAreaMargin), - paddingTop: getCombinedSpacing(modalContainerStylePaddingTop, safeAreaPaddingTop, shouldAddTopSafeAreaPadding), - paddingBottom: getCombinedSpacing(modalContainerStylePaddingBottom, safeAreaPaddingBottomWithFallback, shouldAddBottomSafeAreaPadding), + marginTop: getCombinedSpacing(modalContainerStyle.marginTop, safeAreaPaddingTop, shouldAddTopSafeAreaMargin), + marginBottom: getCombinedSpacing(modalContainerStyle.marginBottom, safeAreaPaddingBottomWithFallback, shouldAddBottomSafeAreaMargin), + paddingTop: getCombinedSpacing(modalContainerStyle.paddingTop, safeAreaPaddingTop, shouldAddTopSafeAreaPadding), + paddingBottom: getCombinedSpacing(modalContainerStyle.paddingBottom, safeAreaPaddingBottomWithFallback, shouldAddBottomSafeAreaPadding), paddingLeft: safeAreaPaddingLeft ?? 0, paddingRight: safeAreaPaddingRight ?? 0, };