Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
import {circularDeepEqual, deepEqual} from 'fast-equals';
import {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 useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import PopoverWithMeasuredContentUtils from '@libs/PopoverWithMeasuredContentUtils';
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 @@ -41,7 +20,7 @@ type PopoverWithMeasuredContentProps = Omit<PopoverProps, 'anchorPosition'> & {
* anchor position.
*/

function PopoverWithMeasuredContent({
function PopoverWithMeasuredContentBase({
popoverDimensions = {
height: 0,
width: 0,
Expand Down Expand Up @@ -231,17 +210,10 @@ function PopoverWithMeasuredContent({
style={styles.invisiblePopover}
onLayout={measurePopover}
>
{children}
{(isVisible || prevIsVisible) && children}
</View>
);
}
PopoverWithMeasuredContent.displayName = 'PopoverWithMeasuredContent';

export default React.memo(PopoverWithMeasuredContent, (prevProps, nextProps) => {
if (prevProps.isVisible === nextProps.isVisible && nextProps.isVisible === false) {
return true;
}
return circularDeepEqual(prevProps, nextProps);
});
PopoverWithMeasuredContentBase.displayName = 'PopoverWithMeasuredContentBase';

export type {PopoverWithMeasuredContentProps};
export default PopoverWithMeasuredContentBase;
40 changes: 40 additions & 0 deletions src/components/PopoverWithMeasuredContent/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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 (
<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);
});

export type {PopoverWithMeasuredContentProps};
25 changes: 25 additions & 0 deletions src/components/PopoverWithMeasuredContent/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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;
};

// eslint-disable-next-line import/prefer-default-export
export type {PopoverWithMeasuredContentProps};
Comment on lines +24 to +25
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.

You can export it as default export to get rid of the eslint disable comment

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.

Agree

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
5 changes: 5 additions & 0 deletions tests/ui/components/OnboardingHelpDropdownButtonTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import waitForBatchedUpdatesWithAct from '../../utils/waitForBatchedUpdatesWithA
jest.mock('@libs/actions/Link', () => ({
openExternalLink: jest.fn(),
}));

jest.mock('@hooks/useResponsiveLayout', () => () => ({
isSmallScreenWidth: false,
}));

jest.mock('@libs/Navigation/Navigation', () => ({
navigate: jest.fn(),
}));
Expand Down
Loading