diff --git a/src/components/DatePicker/types.ts b/src/components/DatePicker/types.ts index 25dd1f754714..1e4bafa5f1d9 100644 --- a/src/components/DatePicker/types.ts +++ b/src/components/DatePicker/types.ts @@ -1,4 +1,4 @@ -import type {PopoverWithMeasuredContentProps} from '@components/PopoverWithMeasuredContent'; +import type PopoverWithMeasuredContentProps from '@components/PopoverWithMeasuredContent/types'; import type {BaseTextInputProps} from '@components/TextInput/BaseTextInput/types'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; diff --git a/src/components/PopoverWithMeasuredContent.tsx b/src/components/PopoverWithMeasuredContent/PopoverWithMeasuredContentBase.tsx similarity index 87% rename from src/components/PopoverWithMeasuredContent.tsx rename to src/components/PopoverWithMeasuredContent/PopoverWithMeasuredContentBase.tsx index fb7b8afd9718..ac64e071c813 100644 --- a/src/components/PopoverWithMeasuredContent.tsx +++ b/src/components/PopoverWithMeasuredContent/PopoverWithMeasuredContentBase.tsx @@ -2,6 +2,9 @@ import {circularDeepEqual, deepEqual} from 'fast-equals'; import React, {useContext, useEffect, useMemo, useState} from 'react'; import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; +import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; +import type {PopoverAnchorPosition} from '@components/Modal/types'; +import Popover from '@components/Popover'; import usePrevious from '@hooks/usePrevious'; import useSidePanel from '@hooks/useSidePanel'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -10,31 +13,7 @@ import ComposerFocusManager from '@libs/ComposerFocusManager'; import PopoverWithMeasuredContentUtils from '@libs/PopoverWithMeasuredContentUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import type {AnchorDimensions, AnchorPosition} from '@src/styles'; -import * as ActionSheetAwareScrollView from './ActionSheetAwareScrollView'; -import type {PopoverAnchorPosition} from './Modal/types'; -import Popover from './Popover'; -import type PopoverProps from './Popover/types'; - -type PopoverWithMeasuredContentProps = Omit & { - /** The horizontal and vertical anchors points for the popover */ - anchorPosition: AnchorPosition; - - /** The dimension of anchor component */ - anchorDimensions?: AnchorDimensions; - - /** Whether we should change the vertical position if the popover's position is overflow */ - shouldSwitchPositionIfOverflow?: boolean; - - /** Whether handle navigation back when modal show. */ - shouldHandleNavigationBack?: boolean; - - /** Whether we should should use top side for the anchor positioning */ - shouldMeasureAnchorPositionFromTop?: boolean; - - /** Whether to skip re-measurement when becoming visible (for components with static dimensions) */ - shouldSkipRemeasurement?: boolean; -}; +import type PopoverWithMeasuredContentProps from './types'; /** * This is a convenient wrapper around the regular Popover component that allows us to use a more sophisticated @@ -43,7 +22,7 @@ type PopoverWithMeasuredContentProps = Omit & { * anchor position. */ -function PopoverWithMeasuredContent({ +function PopoverWithMeasuredContentBase({ popoverDimensions = { width: CONST.POPOVER_DROPDOWN_WIDTH, height: CONST.POPOVER_DROPDOWN_MIN_HEIGHT, @@ -87,7 +66,6 @@ function PopoverWithMeasuredContent({ const prevWindowDimensions = usePrevious({availableWidth, windowHeight}); const hasStaticDimensions = popoverDimensions.width > 0 && popoverDimensions.height > 0; - const modalId = useMemo(() => ComposerFocusManager.getId(), []); useEffect(() => { @@ -100,7 +78,6 @@ function PopoverWithMeasuredContent({ if (!prevIsVisible && isVisible && isContentMeasured && !shouldSkipRemeasurement) { // Check if anything significant changed that would require re-measurement const hasAnchorPositionChanged = !deepEqual(prevAnchorPosition, anchorPosition); - const hasWindowSizeChanged = !deepEqual(prevWindowDimensions, {availableWidth, windowHeight}); // Only reset if: @@ -184,7 +161,7 @@ function PopoverWithMeasuredContent({ shouldSwitchPositionIfOverflow, ); return {horizontalShift, verticalShift}; - }, [adjustedAnchorPosition.left, adjustedAnchorPosition.top, popoverWidth, availableWidth, popoverHeight, windowHeight, anchorDimensions.height, shouldSwitchPositionIfOverflow]); + }, [adjustedAnchorPosition.left, adjustedAnchorPosition.top, popoverWidth, popoverHeight, availableWidth, windowHeight, anchorDimensions.height, shouldSwitchPositionIfOverflow]); const shiftedAnchorPosition: PopoverAnchorPosition = useMemo(() => { const result: PopoverAnchorPosition = { @@ -228,7 +205,7 @@ function PopoverWithMeasuredContent({ {...props} anchorPosition={shiftedAnchorPosition} > - {children} + {(isVisible || prevIsVisible) && children} ) : ( /* @@ -245,13 +222,11 @@ function PopoverWithMeasuredContent({ ); } -PopoverWithMeasuredContent.displayName = 'PopoverWithMeasuredContent'; +PopoverWithMeasuredContentBase.displayName = 'PopoverWithMeasuredContentBase'; -export default React.memo(PopoverWithMeasuredContent, (prevProps, nextProps) => { +export default React.memo(PopoverWithMeasuredContentBase, (prevProps, nextProps) => { if (prevProps.isVisible === nextProps.isVisible && nextProps.isVisible === false) { return true; } return circularDeepEqual(prevProps, nextProps); }); - -export type {PopoverWithMeasuredContentProps}; diff --git a/src/components/PopoverWithMeasuredContent/index.tsx b/src/components/PopoverWithMeasuredContent/index.tsx new file mode 100644 index 000000000000..55d7a7e46f64 --- /dev/null +++ b/src/components/PopoverWithMeasuredContent/index.tsx @@ -0,0 +1,38 @@ +import {circularDeepEqual} from 'fast-equals'; +import React from 'react'; +import Modal from '@components/Modal'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import CONST from '@src/CONST'; +import PopoverWithMeasuredContentBase from './PopoverWithMeasuredContentBase'; +import type PopoverWithMeasuredContentProps from './types'; + +/** + * Logic for PopoverWithMeasuredContent is in PopoverWithMeasuredContentBase. + * This component is a perf optimization, it return BOTTOM_DOCKED early, for small screens avoiding Popover measurement logic calculations. + */ +function PopoverWithMeasuredContent({...props}: PopoverWithMeasuredContentProps) { + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + if (isSmallScreenWidth) { + return ( + + ); + } + + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +} +PopoverWithMeasuredContent.displayName = 'PopoverWithMeasuredContent'; + +export default React.memo(PopoverWithMeasuredContent, (prevProps, nextProps) => { + if (prevProps.isVisible === nextProps.isVisible && nextProps.isVisible === false) { + return true; + } + return circularDeepEqual(prevProps, nextProps); +}); diff --git a/src/components/PopoverWithMeasuredContent/types.ts b/src/components/PopoverWithMeasuredContent/types.ts new file mode 100644 index 000000000000..a95e363db036 --- /dev/null +++ b/src/components/PopoverWithMeasuredContent/types.ts @@ -0,0 +1,24 @@ +import type PopoverProps from '@components/Popover/types'; +import type {AnchorDimensions, AnchorPosition} from '@styles/index'; + +type PopoverWithMeasuredContentProps = Omit & { + /** The horizontal and vertical anchors points for the popover */ + anchorPosition: AnchorPosition; + + /** The dimension of anchor component */ + anchorDimensions?: AnchorDimensions; + + /** Whether we should change the vertical position if the popover's position is overflow */ + shouldSwitchPositionIfOverflow?: boolean; + + /** Whether handle navigation back when modal show. */ + shouldHandleNavigationBack?: boolean; + + /** Whether we should should use top side for the anchor positioning */ + shouldMeasureAnchorPositionFromTop?: boolean; + + /** Whether to skip re-measurement when becoming visible (for components with static dimensions) */ + shouldSkipRemeasurement?: boolean; +}; + +export default PopoverWithMeasuredContentProps; diff --git a/src/components/Search/FilterDropdowns/DropdownButton.tsx b/src/components/Search/FilterDropdowns/DropdownButton.tsx index 88ec3ec4abab..9bedb79d2d46 100644 --- a/src/components/Search/FilterDropdowns/DropdownButton.tsx +++ b/src/components/Search/FilterDropdowns/DropdownButton.tsx @@ -103,9 +103,6 @@ function DropdownButton({label, value, viewportOffsetTop, PopoverComponent}: Dro }, [isSmallScreenWidth, styles]); const popoverContent = useMemo(() => { - if (!isOverlayVisible) { - return null; - } return PopoverComponent({closeOverlay: toggleOverlay}); // PopoverComponent is stable so we don't need it here as a dep. // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps diff --git a/tests/ui/components/OnboardingHelpDropdownButtonTest.tsx b/tests/ui/components/OnboardingHelpDropdownButtonTest.tsx index d50ade4e6e2d..d375be78b19e 100644 --- a/tests/ui/components/OnboardingHelpDropdownButtonTest.tsx +++ b/tests/ui/components/OnboardingHelpDropdownButtonTest.tsx @@ -27,6 +27,10 @@ jest.mock('@libs/actions/ScheduleCall', () => ({ cancelBooking: jest.fn(), })); +jest.mock('@hooks/useResponsiveLayout', () => () => ({ + isSmallScreenWidth: false, +})); + const mockOpenExternalLink = jest.mocked(openExternalLink); const mockNavigate = jest.mocked(Navigation.navigate); const mockClearBookingDraft = jest.mocked(clearBookingDraft);