Skip to content

Commit 73e3884

Browse files
authored
Merge pull request #57920 from bernhardoj/fix/57011-modal-opens-at-bottom-page
Fix wrong popover position
2 parents dc0e915 + 61ed05e commit 73e3884

File tree

8 files changed

+122
-105
lines changed

8 files changed

+122
-105
lines changed

src/components/ThreeDotsMenu/index.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import useLocalize from '@hooks/useLocalize';
1212
import useTheme from '@hooks/useTheme';
1313
import useThemeStyles from '@hooks/useThemeStyles';
1414
import {isMobile} from '@libs/Browser';
15+
import type {AnchorPosition} from '@styles/index';
1516
import variables from '@styles/variables';
1617
import CONST from '@src/CONST';
1718
import ONYXKEYS from '@src/ONYXKEYS';
@@ -29,6 +30,7 @@ function ThreeDotsMenu({
2930
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
3031
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP
3132
},
33+
getAnchorPosition,
3234
shouldOverlay = false,
3335
shouldSetModalVisibility = true,
3436
disabled = false,
@@ -42,6 +44,7 @@ function ThreeDotsMenu({
4244
const theme = useTheme();
4345
const styles = useThemeStyles();
4446
const [isPopupMenuVisible, setPopupMenuVisible] = useState(false);
47+
const [position, setPosition] = useState<AnchorPosition>();
4548
const buttonRef = useRef<View>(null);
4649
const {translate} = useLocalize();
4750
const isBehindModal = modal?.willAlertModalBecomeVisible && !modal?.isPopover && !shouldOverlay;
@@ -68,10 +71,17 @@ function ThreeDotsMenu({
6871
}
6972
hideProductTrainingTooltip?.();
7073
buttonRef.current?.blur();
71-
showPopoverMenu();
72-
if (onIconPress) {
73-
onIconPress();
74+
75+
if (getAnchorPosition) {
76+
getAnchorPosition().then((value) => {
77+
setPosition(value);
78+
showPopoverMenu();
79+
});
80+
} else {
81+
showPopoverMenu();
7482
}
83+
84+
onIconPress?.();
7585
};
7686

7787
const TooltipToRender = shouldShowProductTrainingTooltip ? EducationalTooltip : Tooltip;
@@ -121,7 +131,7 @@ function ThreeDotsMenu({
121131
<PopoverMenu
122132
onClose={hidePopoverMenu}
123133
isVisible={isPopupMenuVisible && !isBehindModal}
124-
anchorPosition={anchorPosition}
134+
anchorPosition={position ?? anchorPosition ?? {horizontal: 0, vertical: 0}}
125135
anchorAlignment={anchorAlignment}
126136
onItemSelected={hidePopoverMenu}
127137
menuItems={menuItems}

src/components/ThreeDotsMenu/types.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {LayoutRectangle, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native';
1+
import type {StyleProp, ViewStyle} from 'react-native';
22
import type {PopoverMenuItem} from '@components/PopoverMenu';
33
import type {TranslationPaths} from '@src/languages/types';
44
import type {AnchorPosition} from '@src/styles';
@@ -24,9 +24,6 @@ type ThreeDotsMenuProps = {
2424
/** menuItems that'll show up on toggle of the popup menu */
2525
menuItems: PopoverMenuItem[];
2626

27-
/** The anchor position of the menu */
28-
anchorPosition: AnchorPosition;
29-
3027
/** The anchor alignment of the menu */
3128
anchorAlignment?: AnchorAlignment;
3229

@@ -52,7 +49,20 @@ type ThreeDotsMenuProps = {
5249
isNested?: boolean;
5350
};
5451

55-
type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>;
52+
type ThreeDotsMenuWithOptionalAnchorProps =
53+
| (ThreeDotsMenuProps & {
54+
/** The anchor position of the menu */
55+
anchorPosition: AnchorPosition;
56+
57+
/** A callback to get the anchor position dynamically */
58+
getAnchorPosition?: never;
59+
})
60+
| (ThreeDotsMenuProps & {
61+
/** The anchor position of the menu */
62+
anchorPosition?: never;
63+
64+
/** A callback to get the anchor position dynamically */
65+
getAnchorPosition: () => Promise<AnchorPosition>;
66+
});
5667

57-
export type {LayoutChangeEventWithTarget};
58-
export default ThreeDotsMenuProps;
68+
export default ThreeDotsMenuWithOptionalAnchorProps;

src/pages/Search/SavedSearchItemThreeDotMenu.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, {useRef, useState} from 'react';
2-
import type {LayoutChangeEvent, LayoutRectangle, NativeSyntheticEvent} from 'react-native';
1+
import React, {useCallback, useRef} from 'react';
32
import {View} from 'react-native';
43
import type {PopoverMenuItem} from '@components/PopoverMenu';
54
import ThreeDotsMenu from '@components/ThreeDotsMenu';
5+
import useResponsiveLayout from '@hooks/useResponsiveLayout';
66
import useThemeStyles from '@hooks/useThemeStyles';
7+
import type {AnchorPosition} from '@styles/index';
78
import CONST from '@src/CONST';
89

910
type SavedSearchItemThreeDotMenuProps = {
@@ -14,29 +15,33 @@ type SavedSearchItemThreeDotMenuProps = {
1415
shouldRenderTooltip: boolean;
1516
};
1617

17-
type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>;
18-
1918
function SavedSearchItemThreeDotMenu({menuItems, isDisabledItem, hideProductTrainingTooltip, renderTooltipContent, shouldRenderTooltip}: SavedSearchItemThreeDotMenuProps) {
2019
const threeDotsMenuContainerRef = useRef<View>(null);
21-
const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0});
20+
const {shouldUseNarrowLayout} = useResponsiveLayout();
2221
const styles = useThemeStyles();
22+
23+
const calculateAndSetThreeDotsMenuPosition = useCallback(() => {
24+
if (shouldUseNarrowLayout) {
25+
return Promise.resolve({horizontal: 0, vertical: 0});
26+
}
27+
return new Promise<AnchorPosition>((resolve) => {
28+
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width) => {
29+
resolve({
30+
horizontal: x + width,
31+
vertical: y,
32+
});
33+
});
34+
});
35+
}, [shouldUseNarrowLayout]);
36+
2337
return (
2438
<View
2539
ref={threeDotsMenuContainerRef}
2640
style={[isDisabledItem && styles.pointerEventsNone]}
27-
onLayout={(e: LayoutChangeEvent) => {
28-
const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target;
29-
target?.measureInWindow((x, y, width) => {
30-
setThreeDotsMenuPosition({
31-
horizontal: x + width,
32-
vertical: y,
33-
});
34-
});
35-
}}
3641
>
3742
<ThreeDotsMenu
3843
menuItems={menuItems}
39-
anchorPosition={threeDotsMenuPosition}
44+
getAnchorPosition={calculateAndSetThreeDotsMenuPosition}
4045
renderProductTrainingTooltipContent={renderTooltipContent}
4146
shouldShowProductTrainingTooltip={shouldRenderTooltip}
4247
anchorAlignment={{

src/pages/settings/Subscription/CardSection/CardSectionActions/index.tsx

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import React, {useMemo, useRef, useState} from 'react';
2-
import type {LayoutChangeEvent} from 'react-native';
1+
import React, {useCallback, useMemo, useRef} from 'react';
32
import {View} from 'react-native';
43
import * as Expensicons from '@components/Icon/Expensicons';
54
import ThreeDotsMenu from '@components/ThreeDotsMenu';
65
import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types';
7-
import type {LayoutChangeEventWithTarget} from '@components/ThreeDotsMenu/types';
86
import useLocalize from '@hooks/useLocalize';
97
import useResponsiveLayout from '@hooks/useResponsiveLayout';
108
import Navigation from '@navigation/Navigation';
@@ -20,7 +18,6 @@ const anchorAlignment = {
2018
function CardSectionActions() {
2119
const {shouldUseNarrowLayout} = useResponsiveLayout();
2220
const {translate} = useLocalize();
23-
const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0});
2421
const threeDotsMenuContainerRef = useRef<View>(null);
2522

2623
const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo(
@@ -39,25 +36,25 @@ function CardSectionActions() {
3936
[translate],
4037
);
4138

42-
return (
43-
<View
44-
ref={threeDotsMenuContainerRef}
45-
onLayout={(e: LayoutChangeEvent) => {
46-
if (shouldUseNarrowLayout) {
47-
return;
48-
}
49-
const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target;
50-
target?.measureInWindow((x, y, width) => {
51-
setThreeDotsMenuPosition({
52-
horizontal: x + width,
53-
vertical: y,
54-
});
39+
const calculateAndSetThreeDotsMenuPosition = useCallback(() => {
40+
if (shouldUseNarrowLayout) {
41+
return Promise.resolve({horizontal: 0, vertical: 0});
42+
}
43+
return new Promise<AnchorPosition>((resolve) => {
44+
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
45+
resolve({
46+
horizontal: x + width,
47+
vertical: y + height,
5548
});
56-
}}
57-
>
49+
});
50+
});
51+
}, [shouldUseNarrowLayout]);
52+
53+
return (
54+
<View ref={threeDotsMenuContainerRef}>
5855
<ThreeDotsMenu
56+
getAnchorPosition={calculateAndSetThreeDotsMenuPosition}
5957
menuItems={overflowMenu}
60-
anchorPosition={threeDotsMenuPosition}
6158
anchorAlignment={anchorAlignment}
6259
shouldOverlay
6360
/>

src/pages/settings/Subscription/SubscriptionDetails/index.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import Text from '@components/Text';
1414
import useLocalize from '@hooks/useLocalize';
1515
import useThemeIllustrations from '@hooks/useThemeIllustrations';
1616
import useThemeStyles from '@hooks/useThemeStyles';
17+
import {clearUpdateSubscriptionSizeError, updateSubscriptionType} from '@libs/actions/Subscription';
1718
import Navigation from '@libs/Navigation/Navigation';
1819
import TaxExemptActions from '@pages/settings/Subscription/TaxExemptActions';
1920
import variables from '@styles/variables';
20-
import * as Subscription from '@userActions/Subscription';
2121
import type {SubscriptionType} from '@src/CONST';
2222
import CONST from '@src/CONST';
2323
import ONYXKEYS from '@src/ONYXKEYS';
@@ -57,7 +57,7 @@ function SubscriptionDetails() {
5757
return;
5858
}
5959

60-
Subscription.updateSubscriptionType(option);
60+
updateSubscriptionType(option);
6161
};
6262

6363
const onSubscriptionSizePress = () => {
@@ -75,9 +75,7 @@ function SubscriptionDetails() {
7575
<OfflineWithFeedback
7676
pendingAction={privateSubscription?.pendingFields?.userCount}
7777
errors={privateSubscription?.errorFields?.userCount}
78-
onClose={() => {
79-
Subscription.clearUpdateSubscriptionSizeError();
80-
}}
78+
onClose={clearUpdateSubscriptionSizeError}
8179
>
8280
<MenuItemWithTopDescription
8381
description={translate('subscription.details.subscriptionSize')}

src/pages/settings/Subscription/TaxExemptActions/index.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import React, {useMemo, useRef, useState} from 'react';
1+
import React, {useCallback, useMemo, useRef} from 'react';
22
import {View} from 'react-native';
3-
import type {LayoutChangeEvent} from 'react-native';
43
import * as Expensicons from '@components/Icon/Expensicons';
54
import ThreeDotsMenu from '@components/ThreeDotsMenu';
65
import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types';
7-
import type {LayoutChangeEventWithTarget} from '@components/ThreeDotsMenu/types';
86
import useLocalize from '@hooks/useLocalize';
97
import useResponsiveLayout from '@hooks/useResponsiveLayout';
108
import useThemeStyles from '@hooks/useThemeStyles';
@@ -22,7 +20,6 @@ function TaxExemptActions() {
2220
const {shouldUseNarrowLayout} = useResponsiveLayout();
2321
const styles = useThemeStyles();
2422
const {translate} = useLocalize();
25-
const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0});
2623
const threeDotsMenuContainerRef = useRef<View>(null);
2724

2825
const overflowMenu: ThreeDotsMenuProps['menuItems'] = useMemo(
@@ -40,26 +37,28 @@ function TaxExemptActions() {
4037
[translate],
4138
);
4239

40+
const calculateAndSetThreeDotsMenuPosition = useCallback(() => {
41+
if (shouldUseNarrowLayout) {
42+
return Promise.resolve({horizontal: 0, vertical: 0});
43+
}
44+
return new Promise<AnchorPosition>((resolve) => {
45+
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
46+
resolve({
47+
horizontal: x + width,
48+
vertical: y + height,
49+
});
50+
});
51+
});
52+
}, [shouldUseNarrowLayout]);
53+
4354
return (
4455
<View
4556
ref={threeDotsMenuContainerRef}
4657
style={[styles.mtn2, styles.pAbsolute, styles.rn3]}
47-
onLayout={(e: LayoutChangeEvent) => {
48-
if (shouldUseNarrowLayout) {
49-
return;
50-
}
51-
const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target;
52-
target?.measureInWindow((x, y, width) => {
53-
setThreeDotsMenuPosition({
54-
horizontal: x + width,
55-
vertical: y,
56-
});
57-
});
58-
}}
5958
>
6059
<ThreeDotsMenu
60+
getAnchorPosition={calculateAndSetThreeDotsMenuPosition}
6161
menuItems={overflowMenu}
62-
anchorPosition={threeDotsMenuPosition}
6362
anchorAlignment={anchorAlignment}
6463
shouldOverlay
6564
/>

src/pages/workspace/WorkspacesListRow.tsx

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {Str} from 'expensify-common';
2-
import React, {useRef, useState} from 'react';
2+
import React, {useCallback, useRef} from 'react';
33
import {View} from 'react-native';
4-
import type {LayoutChangeEvent, StyleProp, ViewStyle} from 'react-native';
4+
import type {StyleProp, ViewStyle} from 'react-native';
55
import type {ValueOf} from 'type-fest';
66
import Avatar from '@components/Avatar';
77
import Badge from '@components/Badge';
@@ -11,7 +11,6 @@ import * as Illustrations from '@components/Icon/Illustrations';
1111
import type {PopoverMenuItem} from '@components/PopoverMenu';
1212
import Text from '@components/Text';
1313
import ThreeDotsMenu from '@components/ThreeDotsMenu';
14-
import type {LayoutChangeEventWithTarget} from '@components/ThreeDotsMenu/types';
1514
import Tooltip from '@components/Tooltip';
1615
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
1716
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
@@ -118,12 +117,25 @@ function WorkspacesListRow({
118117
}: WorkspacesListRowProps) {
119118
const styles = useThemeStyles();
120119
const {translate} = useLocalize();
121-
const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0});
122120
const threeDotsMenuContainerRef = useRef<View>(null);
123121
const {shouldUseNarrowLayout} = useResponsiveLayout();
124122

125123
const ownerDetails = ownerAccountID && getPersonalDetailsByIDs({accountIDs: [ownerAccountID], currentUserAccountID: currentUserPersonalDetails.accountID}).at(0);
126124

125+
const calculateAndSetThreeDotsMenuPosition = useCallback(() => {
126+
if (shouldUseNarrowLayout) {
127+
return Promise.resolve({horizontal: 0, vertical: 0});
128+
}
129+
return new Promise<AnchorPosition>((resolve) => {
130+
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => {
131+
resolve({
132+
horizontal: x + width,
133+
vertical: y + height,
134+
});
135+
});
136+
});
137+
}, [shouldUseNarrowLayout]);
138+
127139
if (layoutWidth === CONST.LAYOUT_WIDTH.NONE) {
128140
// To prevent layout from jumping or rendering for a split second, when
129141
// isWide is undefined we don't assume anything and simply return null.
@@ -167,24 +179,10 @@ function WorkspacesListRow({
167179
<View style={[styles.flexRow, styles.gap2, styles.alignItemsCenter, isNarrow && styles.workspaceListRBR]}>
168180
<BrickRoadIndicatorIcon brickRoadIndicator={brickRoadIndicator} />
169181
</View>
170-
<View
171-
ref={threeDotsMenuContainerRef}
172-
onLayout={(e: LayoutChangeEvent) => {
173-
if (shouldUseNarrowLayout) {
174-
return;
175-
}
176-
const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target;
177-
target?.measureInWindow((x, y, width) => {
178-
setThreeDotsMenuPosition({
179-
horizontal: x + width,
180-
vertical: y,
181-
});
182-
});
183-
}}
184-
>
182+
<View ref={threeDotsMenuContainerRef}>
185183
<ThreeDotsMenu
184+
getAnchorPosition={calculateAndSetThreeDotsMenuPosition}
186185
menuItems={menuItems}
187-
anchorPosition={threeDotsMenuPosition}
188186
anchorAlignment={{horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP}}
189187
shouldOverlay
190188
disabled={shouldDisableThreeDotsMenu}

0 commit comments

Comments
 (0)