diff --git a/src/components/PopoverWithMeasuredContent.js b/src/components/PopoverWithMeasuredContent.tsx similarity index 52% rename from src/components/PopoverWithMeasuredContent.js rename to src/components/PopoverWithMeasuredContent.tsx index 7de5be113e53..9d10f7869f8a 100644 --- a/src/components/PopoverWithMeasuredContent.js +++ b/src/components/PopoverWithMeasuredContent.tsx @@ -1,56 +1,17 @@ -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 type {AnchorPosition} from '@src/styles'; import Popover from './Popover'; -import {defaultProps as defaultPopoverProps, propTypes as popoverPropTypes} from './Popover/popoverPropTypes'; -import {windowDimensionsPropTypes} from './withWindowDimensions'; - -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)]), +import {PopoverProps} from './Popover/types'; +type PopoverWithMeasuredContentProps = Omit & { /** 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, - }), -}; - -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, + anchorPosition: AnchorPosition; }; /** @@ -60,32 +21,44 @@ 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 [isPopoverVisible, setIsPopoverVisible] = 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. + * Skip render on Popover until recalculations are done by setting isContentMeasured to false as early as possible. */ - if (!isVisible && props.isVisible) { + if (!isPopoverVisible && 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); + setIsPopoverVisible(true); + } else if (isPopoverVisible && !isVisible) { + setIsPopoverVisible(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); @@ -93,40 +66,40 @@ function PopoverWithMeasuredContent(props) { 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); @@ -136,11 +109,15 @@ function PopoverWithMeasuredContent(props) { }; return isContentMeasured ? ( - {props.children} + {children} ) : ( /* @@ -153,18 +130,15 @@ function PopoverWithMeasuredContent(props) { style={styles.invisiblePopover} onLayout={measurePopover} > - {props.children} + {children} ); } - -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); }); diff --git a/src/libs/calculateAnchorPosition.ts b/src/libs/calculateAnchorPosition.ts index 8db4c053aba6..b9d0f553ca4f 100644 --- a/src/libs/calculateAnchorPosition.ts +++ b/src/libs/calculateAnchorPosition.ts @@ -2,6 +2,7 @@ import {View} from 'react-native'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; +import type {AnchorPosition} from '@src/styles'; type AnchorOrigin = { horizontal: ValueOf; @@ -9,11 +10,6 @@ type AnchorOrigin = { shiftVertical?: number; }; -type AnchorPosition = { - horizontal: number; - vertical: number; -}; - /** * Gets the x,y position of the passed in component for the purpose of anchoring another component to it. */ diff --git a/src/styles/index.ts b/src/styles/index.ts index 8cf880d6bef6..8802250810fa 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4133,4 +4133,4 @@ const defaultStyles = styles(defaultTheme); export default styles; export {defaultStyles}; -export type {Styles, ThemeStyles, StatusBarStyle, ColorScheme}; +export type {Styles, ThemeStyles, StatusBarStyle, ColorScheme, AnchorPosition};