Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'PopoverWithMeasuredContent.js' component to TypeScript #32551

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion src/components/Popover/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ type PopoverProps = BaseModalProps & {

type PopoverWithWindowDimensionsProps = PopoverProps & WindowDimensionsProps;

export type {PopoverProps, PopoverWithWindowDimensionsProps};
export type {PopoverProps, PopoverWithWindowDimensionsProps, AnchorAlignment, PopoverDimensions};
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to export those types? AnchorAlignment and PopoverDimensions aren't used in any other file it seems

Copy link
Member Author

Choose a reason for hiding this comment

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

reuse added

Original file line number Diff line number Diff line change
@@ -1,56 +1,21 @@
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import React, {useMemo, useState} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import {LayoutChangeEvent, View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import PopoverWithMeasuredContentUtils from '@libs/PopoverWithMeasuredContentUtils';
import CONST from '@src/CONST';
import Popover from './Popover';
import {defaultProps as defaultPopoverProps, propTypes as popoverPropTypes} from './Popover/popoverPropTypes';
import {windowDimensionsPropTypes} from './withWindowDimensions';
import {PopoverProps} from './Popover/types';

const propTypes = {
// All popover props except:
// 1) anchorPosition (which is overridden for this component)
// 2) windowDimensionsPropTypes - we exclude them because we use useWindowDimensions hook instead
..._.omit(popoverPropTypes, ['anchorPosition', ..._.keys(windowDimensionsPropTypes)]),

/** The horizontal and vertical anchors points for the popover */
anchorPosition: PropTypes.shape({
horizontal: PropTypes.number.isRequired,
vertical: PropTypes.number.isRequired,
}).isRequired,

/** How the popover should be aligned. The value you passed will is the part of the component that will be aligned to the
* anchorPosition. ie: vertical:top means the top of the menu will be positioned in the anchorPosition */
anchorAlignment: PropTypes.shape({
horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)),
vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)),
}),

/** Static dimensions for the popover.
* Note: When passed, it will skip dimensions measuring of the popover, and provided dimensions will be used to calculate the anchor position.
*/
popoverDimensions: PropTypes.shape({
height: PropTypes.number,
width: PropTypes.number,
}),
type AnchorPosition = {
Copy link
Contributor

Choose a reason for hiding this comment

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

We define this type in 3 different files (styles/index.ts, libs/calculateAnchorPosition.ts and PopoverWithMeasuredContent)
Let's move it to src/components/Popover/types.ts and export it to those 3 files

Copy link
Member Author

Choose a reason for hiding this comment

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

type reusing added

horizontal: number;
vertical: number;
};

const defaultProps = {
...defaultPopoverProps,

// Default positioning of the popover
anchorAlignment: {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
},
popoverDimensions: {
height: 0,
width: 0,
},
withoutOverlay: false,
type PopoverWithMeasuredContentProps = Omit<PopoverProps, 'anchorPosition'> & {
/** The horizontal and vertical anchors points for the popover */
anchorPosition: AnchorPosition;
};

/**
Expand All @@ -60,73 +25,85 @@ const defaultProps = {
* anchor position.
*/

function PopoverWithMeasuredContent(props) {
function PopoverWithMeasuredContent({
popoverDimensions = {
height: 0,
width: 0,
},
anchorPosition,
isVisible,
anchorAlignment = {
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
},
children,
withoutOverlay = false,
...props
}: PopoverWithMeasuredContentProps) {
const styles = useThemeStyles();
const {windowWidth, windowHeight} = useWindowDimensions();
const [popoverWidth, setPopoverWidth] = useState(props.popoverDimensions.width);
const [popoverHeight, setPopoverHeight] = useState(props.popoverDimensions.height);
const [popoverWidth, setPopoverWidth] = useState(popoverDimensions.width);
const [popoverHeight, setPopoverHeight] = useState(popoverDimensions.height);
const [isContentMeasured, setIsContentMeasured] = useState(popoverWidth > 0 && popoverHeight > 0);
const [isVisible, setIsVisible] = useState(false);
const [isCurrentVisible, setIsCurrentVisible] = useState(false);

/**
* When Popover becomes visible, we need to recalculate the Dimensions.
* Skip render on Popover until recalculations have done by setting isContentMeasured false as early as possible.
*/
if (!isVisible && props.isVisible) {
if (!isCurrentVisible && isVisible) {
// When Popover is shown recalculate
setIsContentMeasured(props.popoverDimensions.width > 0 && props.popoverDimensions.height > 0);
setIsVisible(true);
} else if (isVisible && !props.isVisible) {
setIsVisible(false);
setIsContentMeasured(popoverDimensions.width > 0 && popoverDimensions.height > 0);
setIsCurrentVisible(true);
} else if (isCurrentVisible && !isVisible) {
setIsCurrentVisible(false);
}

/**
* Measure the size of the popover's content.
*
* @param {Object} nativeEvent
*/
const measurePopover = ({nativeEvent}) => {
const measurePopover = ({nativeEvent}: LayoutChangeEvent) => {
setPopoverWidth(nativeEvent.layout.width);
setPopoverHeight(nativeEvent.layout.height);
setIsContentMeasured(true);
};

const adjustedAnchorPosition = useMemo(() => {
let horizontalConstraint;
switch (props.anchorAlignment.horizontal) {
switch (anchorAlignment.horizontal) {
case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT:
horizontalConstraint = {left: props.anchorPosition.horizontal - popoverWidth};
horizontalConstraint = {left: anchorPosition.horizontal - popoverWidth};
break;
case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.CENTER:
horizontalConstraint = {
left: Math.floor(props.anchorPosition.horizontal - popoverWidth / 2),
left: Math.floor(anchorPosition.horizontal - popoverWidth / 2),
};
break;
case CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT:
default:
horizontalConstraint = {left: props.anchorPosition.horizontal};
horizontalConstraint = {left: anchorPosition.horizontal};
}

let verticalConstraint;
switch (props.anchorAlignment.vertical) {
switch (anchorAlignment.vertical) {
case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM:
verticalConstraint = {top: props.anchorPosition.vertical - popoverHeight};
verticalConstraint = {top: anchorPosition.vertical - popoverHeight};
break;
case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.CENTER:
verticalConstraint = {
top: Math.floor(props.anchorPosition.vertical - popoverHeight / 2),
top: Math.floor(anchorPosition.vertical - popoverHeight / 2),
};
break;
case CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP:
default:
verticalConstraint = {top: props.anchorPosition.vertical};
verticalConstraint = {top: anchorPosition.vertical};
}

return {
...horizontalConstraint,
...verticalConstraint,
};
}, [props.anchorPosition, props.anchorAlignment, popoverWidth, popoverHeight]);
}, [anchorPosition, anchorAlignment, popoverWidth, popoverHeight]);

const horizontalShift = PopoverWithMeasuredContentUtils.computeHorizontalShift(adjustedAnchorPosition.left, popoverWidth, windowWidth);
const verticalShift = PopoverWithMeasuredContentUtils.computeVerticalShift(adjustedAnchorPosition.top, popoverHeight, windowHeight);
Expand All @@ -136,11 +113,15 @@ function PopoverWithMeasuredContent(props) {
};
return isContentMeasured ? (
<Popover
popoverDimensions={popoverDimensions}
anchorAlignment={anchorAlignment}
isVisible={isVisible}
withoutOverlay={withoutOverlay}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
anchorPosition={shiftedAnchorPosition}
>
<View onLayout={measurePopover}>{props.children}</View>
<View onLayout={measurePopover}>{children}</View>
</Popover>
) : (
/*
Expand All @@ -153,18 +134,15 @@ function PopoverWithMeasuredContent(props) {
style={styles.invisiblePopover}
onLayout={measurePopover}
>
{props.children}
{children}
</View>
);
}

PopoverWithMeasuredContent.propTypes = propTypes;
PopoverWithMeasuredContent.defaultProps = defaultProps;
PopoverWithMeasuredContent.displayName = 'PopoverWithMeasuredContent';

export default React.memo(PopoverWithMeasuredContent, (prevProps, nextProps) => {
if (prevProps.isVisible === nextProps.isVisible && nextProps.isVisible === false) {
return true;
}
return _.isEqual(prevProps, nextProps);
return isEqual(prevProps, nextProps);
});