-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Fix wrong popover position #57920
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
Fix wrong popover position #57920
Changes from 3 commits
9811f2e
fd41d62
8302ac1
544b47f
868fdef
67eee8b
a90224f
4d9339b
61ed05e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,4 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type {LayoutRectangle, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type {StyleProp, ViewStyle} from 'react-native'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type {PopoverMenuItem} from '@components/PopoverMenu'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type {TranslationPaths} from '@src/languages/types'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type {AnchorPosition} from '@src/styles'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -24,9 +24,6 @@ type ThreeDotsMenuProps = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** menuItems that'll show up on toggle of the popup menu */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
menuItems: PopoverMenuItem[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** The anchor position of the menu */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anchorPosition: AnchorPosition; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** The anchor alignment of the menu */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anchorAlignment?: AnchorAlignment; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -52,7 +49,20 @@ type ThreeDotsMenuProps = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isNested?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type LayoutChangeEventWithTarget = NativeSyntheticEvent<{layout: LayoutRectangle; target: HTMLElement}>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type ThreeDotsMenuWithOptionalAnchorProps = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (ThreeDotsMenuProps & { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** The anchor position of the menu */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anchorPosition: AnchorPosition; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** A callback to get the anchor position dynamically */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
getAnchorPosition?: () => Promise<AnchorPosition>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (ThreeDotsMenuProps & { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** The anchor position of the menu */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
anchorPosition?: AnchorPosition; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** A callback to get the anchor position dynamically */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
getAnchorPosition: () => Promise<AnchorPosition>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the difference between both types? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First type requires anchorPosition, second type requires getAnchorPosition. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's choose one. I think getAnchorPosition is good enough. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bernhardoj Is there a need for both props? Should we just keep one There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need both. With this type, the user needs to either pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmm, yeah it is a little overhead of promise but having two props for same thing while a single prop can handle both cases is confusing. While you must have noticed, we have a few components which are consuming a huge number of props making is very confusing on how these components can be utilized There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think some components have too many props because they're too big, trying to handle many cases/responsibilities. If we use
for component that already has the anchor position information. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I am fine with it. It's not a blocker. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, should the user pass only one or both can be passed? In case, only one should be set. We can update the type to
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export type {LayoutChangeEventWithTarget}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default ThreeDotsMenuProps; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default ThreeDotsMenuWithOptionalAnchorProps; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,8 @@ | ||
import React, {useMemo, useRef, useState} from 'react'; | ||
import React, {useCallback, useMemo, useRef} from 'react'; | ||
import {View} from 'react-native'; | ||
import type {LayoutChangeEvent} from 'react-native'; | ||
import * as Expensicons from '@components/Icon/Expensicons'; | ||
import ThreeDotsMenu from '@components/ThreeDotsMenu'; | ||
import type ThreeDotsMenuProps from '@components/ThreeDotsMenu/types'; | ||
import type {LayoutChangeEventWithTarget} from '@components/ThreeDotsMenu/types'; | ||
import useLocalize from '@hooks/useLocalize'; | ||
import useResponsiveLayout from '@hooks/useResponsiveLayout'; | ||
import useThemeStyles from '@hooks/useThemeStyles'; | ||
|
@@ -22,7 +20,6 @@ function TaxExemptActions() { | |
const {shouldUseNarrowLayout} = useResponsiveLayout(); | ||
const styles = useThemeStyles(); | ||
const {translate} = useLocalize(); | ||
const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState<AnchorPosition>({horizontal: 0, vertical: 0}); | ||
const threeDotsMenuContainerRef = useRef<View>(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<AnchorPosition>((resolve) => { | ||
threeDotsMenuContainerRef.current?.measureInWindow((x, y, width, height) => { | ||
resolve({ | ||
horizontal: x + width, | ||
vertical: y + height, | ||
}); | ||
}); | ||
}); | ||
}, [shouldUseNarrowLayout]); | ||
|
||
return ( | ||
<View | ||
ref={threeDotsMenuContainerRef} | ||
style={[styles.mtn2, styles.pAbsolute, styles.rn3]} | ||
onLayout={(e: LayoutChangeEvent) => { | ||
if (shouldUseNarrowLayout) { | ||
return; | ||
} | ||
const target = e.target || (e as LayoutChangeEventWithTarget).nativeEvent.target; | ||
target?.measureInWindow((x, y, width) => { | ||
setThreeDotsMenuPosition({ | ||
horizontal: x + width, | ||
vertical: y, | ||
}); | ||
}); | ||
}} | ||
> | ||
<ThreeDotsMenu | ||
getAnchorPosition={calculateAndSetThreeDotsMenuPosition} | ||
menuItems={overflowMenu} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we calculating it from the outside View but not from inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i notice that not all 3 dot has the same calculation. saved search 3 dot for example doesn't add height to the vertical position.
|
||
anchorPosition={threeDotsMenuPosition} | ||
anchorAlignment={anchorAlignment} | ||
shouldOverlay | ||
/> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we passing both
position
andanchorPosition
, when we already passing the anchorPosition callback?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should pass
{horizontal: 0, vertical: 0}
as default value toposition
state.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a component passes
anchorPosition
and notgetAnchorPosition
, then the position will be undefined and fallback toanchorPosition
props.