diff --git a/src/components/ThreeDotsMenu/index.tsx b/src/components/ThreeDotsMenu/index.tsx index 79528b02c56d..e87ada85863f 100644 --- a/src/components/ThreeDotsMenu/index.tsx +++ b/src/components/ThreeDotsMenu/index.tsx @@ -12,6 +12,7 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {isMobile} from '@libs/Browser'; +import type {AnchorPosition} from '@styles/index'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -29,6 +30,7 @@ function ThreeDotsMenu({ horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, + getAnchorPosition, shouldOverlay = false, shouldSetModalVisibility = true, disabled = false, @@ -42,6 +44,7 @@ function ThreeDotsMenu({ const theme = useTheme(); const styles = useThemeStyles(); const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); + const [position, setPosition] = useState(); const buttonRef = useRef(null); const {translate} = useLocalize(); const isBehindModal = modal?.willAlertModalBecomeVisible && !modal?.isPopover && !shouldOverlay; @@ -68,10 +71,17 @@ function ThreeDotsMenu({ } hideProductTrainingTooltip?.(); buttonRef.current?.blur(); - showPopoverMenu(); - if (onIconPress) { - onIconPress(); + + if (getAnchorPosition) { + getAnchorPosition().then((value) => { + setPosition(value); + showPopoverMenu(); + }); + } else { + showPopoverMenu(); } + + onIconPress?.(); }; const TooltipToRender = shouldShowProductTrainingTooltip ? EducationalTooltip : Tooltip; @@ -121,7 +131,7 @@ function ThreeDotsMenu({ ; +type ThreeDotsMenuWithOptionalAnchorProps = + | (ThreeDotsMenuProps & { + /** The anchor position of the menu */ + anchorPosition: AnchorPosition; + + /** A callback to get the anchor position dynamically */ + getAnchorPosition?: never; + }) + | (ThreeDotsMenuProps & { + /** The anchor position of the menu */ + anchorPosition?: never; + + /** A callback to get the anchor position dynamically */ + getAnchorPosition: () => Promise; + }); -export type {LayoutChangeEventWithTarget}; -export default ThreeDotsMenuProps; +export default ThreeDotsMenuWithOptionalAnchorProps; diff --git a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx index 2a0733e40c7e..e5475feaadd5 100644 --- a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx +++ b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx @@ -1,9 +1,10 @@ -import React, {useRef, useState} from 'react'; -import type {LayoutChangeEvent, LayoutRectangle, NativeSyntheticEvent} from 'react-native'; +import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; type SavedSearchItemThreeDotMenuProps = { @@ -14,29 +15,33 @@ type SavedSearchItemThreeDotMenuProps = { shouldRenderTooltip: boolean; }; -type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>; - function SavedSearchItemThreeDotMenu({menuItems, isDisabledItem, hideProductTrainingTooltip, renderTooltipContent, shouldRenderTooltip}: SavedSearchItemThreeDotMenuProps) { const threeDotsMenuContainerRef = useRef(null); - const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0}); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); + + const calculateAndSetThreeDotsMenuPosition = useCallback(() => { + if (shouldUseNarrowLayout) { + return Promise.resolve({horizontal: 0, vertical: 0}); + } + return new Promise((resolve) => { + threeDotsMenuContainerRef.current?.measureInWindow((x, y, width) => { + resolve({ + horizontal: x + width, + vertical: y, + }); + }); + }); + }, [shouldUseNarrowLayout]); + return ( { - const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; - target?.measureInWindow((x, y, width) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y, - }); - }); - }} > ({horizontal: 0, vertical: 0}); const threeDotsMenuContainerRef = useRef(null); const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( @@ -39,25 +36,25 @@ function CardSectionActions() { [translate], ); - return ( - { - if (shouldUseNarrowLayout) { - return; - } - const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; - target?.measureInWindow((x, y, width) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y, - }); + const calculateAndSetThreeDotsMenuPosition = useCallback(() => { + if (shouldUseNarrowLayout) { + return Promise.resolve({horizontal: 0, vertical: 0}); + } + return new Promise((resolve) => { + threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { + resolve({ + horizontal: x + width, + vertical: y + height, }); - }} - > + }); + }); + }, [shouldUseNarrowLayout]); + + return ( + diff --git a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx index 126ae459e723..cde1eb5449b9 100644 --- a/src/pages/settings/Subscription/SubscriptionDetails/index.tsx +++ b/src/pages/settings/Subscription/SubscriptionDetails/index.tsx @@ -14,10 +14,10 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; +import {clearUpdateSubscriptionSizeError, updateSubscriptionType} from '@libs/actions/Subscription'; import Navigation from '@libs/Navigation/Navigation'; import TaxExemptActions from '@pages/settings/Subscription/TaxExemptActions'; import variables from '@styles/variables'; -import * as Subscription from '@userActions/Subscription'; import type {SubscriptionType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -57,7 +57,7 @@ function SubscriptionDetails() { return; } - Subscription.updateSubscriptionType(option); + updateSubscriptionType(option); }; const onSubscriptionSizePress = () => { @@ -75,9 +75,7 @@ function SubscriptionDetails() { { - Subscription.clearUpdateSubscriptionSizeError(); - }} + onClose={clearUpdateSubscriptionSizeError} > ({horizontal: 0, vertical: 0}); const threeDotsMenuContainerRef = useRef(null); const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo( @@ -40,26 +37,28 @@ function TaxExemptActions() { [translate], ); + const calculateAndSetThreeDotsMenuPosition = useCallback(() => { + if (shouldUseNarrowLayout) { + return Promise.resolve({horizontal: 0, vertical: 0}); + } + return new Promise((resolve) => { + threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { + resolve({ + horizontal: x + width, + vertical: y + height, + }); + }); + }); + }, [shouldUseNarrowLayout]); + return ( { - if (shouldUseNarrowLayout) { - return; - } - const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; - target?.measureInWindow((x, y, width) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y, - }); - }); - }} > diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx index 54f8adc4af26..3f1c5e2c03b2 100644 --- a/src/pages/workspace/WorkspacesListRow.tsx +++ b/src/pages/workspace/WorkspacesListRow.tsx @@ -1,7 +1,7 @@ import {Str} from 'expensify-common'; -import React, {useRef, useState} from 'react'; +import React, {useCallback, useRef} from 'react'; import {View} from 'react-native'; -import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; import type {ValueOf} from 'type-fest'; import Avatar from '@components/Avatar'; import Badge from '@components/Badge'; @@ -11,7 +11,6 @@ import * as Illustrations from '@components/Icon/Illustrations'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import Text from '@components/Text'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; -import type {LayoutChangeEventWithTarget} from '@components/ThreeDotsMenu/types'; import Tooltip from '@components/Tooltip'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; @@ -118,12 +117,25 @@ function WorkspacesListRow({ }: WorkspacesListRowProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0}); const threeDotsMenuContainerRef = useRef(null); const {shouldUseNarrowLayout} = useResponsiveLayout(); const ownerDetails = ownerAccountID && getPersonalDetailsByIDs({accountIDs: [ownerAccountID], currentUserAccountID: currentUserPersonalDetails.accountID}).at(0); + const calculateAndSetThreeDotsMenuPosition = useCallback(() => { + if (shouldUseNarrowLayout) { + return Promise.resolve({horizontal: 0, vertical: 0}); + } + return new Promise((resolve) => { + threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { + resolve({ + horizontal: x + width, + vertical: y + height, + }); + }); + }); + }, [shouldUseNarrowLayout]); + if (layoutWidth === CONST.LAYOUT_WIDTH.NONE) { // To prevent layout from jumping or rendering for a split second, when // isWide is undefined we don't assume anything and simply return null. @@ -167,24 +179,10 @@ function WorkspacesListRow({ - { - if (shouldUseNarrowLayout) { - return; - } - const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; - target?.measureInWindow((x, y, width) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y, - }); - }); - }} - > + ({horizontal: 0, vertical: 0}); const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); const [datetimeToRelative, setDateTimeToRelative] = useState(''); const threeDotsMenuContainerRef = useRef(null); @@ -188,6 +185,20 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { getAssignedSupportData(policyID); }, [policyID]); + const calculateAndSetThreeDotsMenuPosition = useCallback(() => { + if (shouldUseNarrowLayout) { + return Promise.resolve({horizontal: 0, vertical: 0}); + } + return new Promise((resolve) => { + threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { + resolve({ + horizontal: x + width, + vertical: y + height, + }); + }); + }); + }, [shouldUseNarrowLayout]); + const integrationSpecificMenuItems = useMemo(() => { const sageIntacctEntityList = policy?.connections?.intacct?.data?.entities ?? []; const netSuiteSubsidiaryList = policy?.connections?.netsuite?.options?.data?.subsidiaryList ?? []; @@ -389,21 +400,10 @@ function PolicyAccountingPage({policy}: PolicyAccountingPageProps) { color={theme.spinner} /> ) : ( - { - const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; - target?.measureInWindow((x, y, width) => { - setThreeDotsMenuPosition({ - horizontal: x + width, - vertical: y, - }); - }); - }} - > +