Skip to content
2 changes: 1 addition & 1 deletion src/components/DatePicker/types.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<PopoverProps, 'anchorPosition'> & {
/** 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
Expand All @@ -43,7 +22,7 @@ type PopoverWithMeasuredContentProps = Omit<PopoverProps, 'anchorPosition'> & {
* anchor position.
*/

function PopoverWithMeasuredContent({
function PopoverWithMeasuredContentBase({
popoverDimensions = {
width: CONST.POPOVER_DROPDOWN_WIDTH,
height: CONST.POPOVER_DROPDOWN_MIN_HEIGHT,
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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:
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -228,7 +205,7 @@ function PopoverWithMeasuredContent({
{...props}
anchorPosition={shiftedAnchorPosition}
>
<View onLayout={measurePopover}>{children}</View>
<View onLayout={measurePopover}>{(isVisible || prevIsVisible) && children}</View>
</Popover>
) : (
/*
Expand All @@ -245,13 +222,11 @@ function PopoverWithMeasuredContent({
</View>
);
}
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};
38 changes: 38 additions & 0 deletions src/components/PopoverWithMeasuredContent/index.tsx
Original file line number Diff line number Diff line change
@@ -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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this reads better

Suggested change
* This component is a perf optimization, it return BOTTOM_DOCKED early, for small screens avoiding Popover measurement logic calculations.
* This component is a perf optimization, it returns a BOTTOM_DOCKED modal 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 (
<Modal
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
type={CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED}
animationIn="slideInUp"
animationOut="slideOutDown"
/>
);
}

// eslint-disable-next-line react/jsx-props-no-spreading
return <PopoverWithMeasuredContentBase {...props} />;
}
PopoverWithMeasuredContent.displayName = 'PopoverWithMeasuredContent';

export default React.memo(PopoverWithMeasuredContent, (prevProps, nextProps) => {
if (prevProps.isVisible === nextProps.isVisible && nextProps.isVisible === false) {
return true;
}
return circularDeepEqual(prevProps, nextProps);
});
24 changes: 24 additions & 0 deletions src/components/PopoverWithMeasuredContent/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type PopoverProps from '@components/Popover/types';
import type {AnchorDimensions, AnchorPosition} from '@styles/index';

type PopoverWithMeasuredContentProps = Omit<PopoverProps, 'anchorPosition'> & {
/** 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;
3 changes: 0 additions & 3 deletions src/components/Search/FilterDropdowns/DropdownButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/ui/components/OnboardingHelpDropdownButtonTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading