Skip to content

Commit a56aa0a

Browse files
author
pac-guerreiro
committed
fix: tooltip positioning for components using position absolute
1 parent fba4f03 commit a56aa0a

File tree

6 files changed

+110
-14
lines changed

6 files changed

+110
-14
lines changed

src/components/Appbar/AppbarHeader.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { useSafeAreaInsets } from 'react-native-safe-area-context';
1313

1414
import { useInternalTheme } from '../../core/theming';
15+
import useInternalNavigation from '../../react-navigation/useInternalNavigation';
1516
import shadow from '../../styles/shadow';
1617
import type { ThemeProp } from '../../types';
1718
import { Appbar } from './Appbar';
@@ -117,6 +118,7 @@ const AppbarHeader = ({
117118
theme: themeOverrides,
118119
...rest
119120
}: Props) => {
121+
const navigation = useInternalNavigation();
120122
const theme = useInternalTheme(themeOverrides);
121123
const { isV3 } = theme;
122124

@@ -134,6 +136,10 @@ const AppbarHeader = ({
134136
zIndex?: number;
135137
};
136138

139+
React.useEffect(() => {
140+
navigation.setParams({ headerHeight: height });
141+
}, [height, navigation]);
142+
137143
const backgroundColor = getAppbarColor(
138144
theme,
139145
elevation,

src/components/Tooltip/Tooltip.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import {
55
LayoutChangeEvent,
66
StyleSheet,
77
Platform,
8+
Pressable,
9+
ViewStyle,
810
} from 'react-native';
911

1012
import type { ThemeProp } from 'src/types';
1113

1214
import { useInternalTheme } from '../../core/theming';
15+
import useAppBarHeight from '../../react-navigation/useAppBarHeight';
1316
import { addEventListener } from '../../utils/addEventListener';
1417
import Portal from '../Portal/Portal';
1518
import Text from '../Typography/Text';
@@ -71,6 +74,7 @@ const Tooltip = ({
7174
theme: themeOverrides,
7275
...rest
7376
}: Props) => {
77+
const headerHeight = useAppBarHeight();
7478
const theme = useInternalTheme(themeOverrides);
7579
const [visible, setVisible] = React.useState(false);
7680

@@ -182,7 +186,11 @@ const Tooltip = ({
182186
backgroundColor: theme.isV3
183187
? theme.colors.onSurface
184188
: theme.colors.tooltip,
185-
...getTooltipPosition(measurement as Measurement),
189+
...getTooltipPosition(
190+
measurement as Measurement,
191+
children,
192+
headerHeight
193+
),
186194
borderRadius: theme.roundness,
187195
...(measurement.measured ? styles.visible : styles.hidden),
188196
},

src/components/Tooltip/utils.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Dimensions, LayoutRectangle } from 'react-native';
1+
import { Dimensions, LayoutRectangle, ViewStyle } from 'react-native';
22

33
type ChildrenMeasurement = {
44
width: number;
@@ -40,18 +40,26 @@ const overflowRight = (center: number, tooltipWidth: number): boolean => {
4040
const overflowBottom = (
4141
childrenY: number,
4242
childrenHeight: number,
43-
tooltipHeight: number
43+
tooltipHeight: number,
44+
headerHeight: number = 0
4445
): boolean => {
4546
const { height: layoutHeight } = Dimensions.get('window');
4647

47-
return childrenY + childrenHeight + tooltipHeight > layoutHeight;
48+
return (
49+
childrenY + childrenHeight + tooltipHeight > layoutHeight - headerHeight
50+
);
4851
};
4952

5053
const getTooltipXPosition = (
5154
{ pageX: childrenX, width: childrenWidth }: ChildrenMeasurement,
5255
{ width: tooltipWidth }: TooltipLayout
5356
): number => {
54-
const center = childrenX + (childrenWidth - tooltipWidth) / 2;
57+
// when the children use position absolute the childrenWidth is measured as 0,
58+
// so it's best to anchor the tooltip at the start of the children
59+
const center =
60+
childrenWidth > 0
61+
? childrenX + (childrenWidth - tooltipWidth) / 2
62+
: childrenX;
5563

5664
if (overflowLeft(center)) return childrenX;
5765

@@ -63,23 +71,64 @@ const getTooltipXPosition = (
6371

6472
const getTooltipYPosition = (
6573
{ pageY: childrenY, height: childrenHeight }: ChildrenMeasurement,
66-
{ height: tooltipHeight }: TooltipLayout
74+
{ height: tooltipHeight }: TooltipLayout,
75+
headerHeight: number = 0
6776
): number => {
68-
if (overflowBottom(childrenY, childrenHeight, tooltipHeight))
77+
if (overflowBottom(childrenY, childrenHeight, tooltipHeight, headerHeight))
6978
return childrenY - tooltipHeight;
7079

7180
return childrenY + childrenHeight;
7281
};
7382

74-
export const getTooltipPosition = ({
75-
children,
76-
tooltip,
77-
measured,
78-
}: Measurement): {} | { left: number; top: number } => {
83+
const getChildrenMeasures = (
84+
style: ViewStyle | Array<ViewStyle>,
85+
measures: ChildrenMeasurement
86+
): ChildrenMeasurement => {
87+
const { position, top, bottom, left, right } = Array.isArray(style)
88+
? style.reduce((acc, current) => ({ ...acc, ...current }))
89+
: style;
90+
91+
if (position === 'absolute') {
92+
let pageX = 0;
93+
let pageY = measures.pageY;
94+
let height = 0;
95+
let width = 0;
96+
if (typeof left === 'number') {
97+
pageX = left;
98+
width = 0;
99+
}
100+
if (typeof right === 'number') {
101+
pageX = measures.width - right;
102+
width = 0;
103+
}
104+
if (typeof top === 'number') {
105+
pageY = pageY + top;
106+
}
107+
if (typeof bottom === 'number') {
108+
pageY = pageY - bottom;
109+
}
110+
111+
return { pageX, pageY, width, height };
112+
}
113+
114+
return measures;
115+
};
116+
117+
export const getTooltipPosition = (
118+
{ children, tooltip, measured }: Measurement,
119+
component: React.ReactElement<{
120+
style: ViewStyle | Array<ViewStyle> | undefined | null;
121+
}>,
122+
headerHeight: number = 0
123+
): {} | { left: number; top: number } => {
79124
if (!measured) return {};
125+
let measures = children;
126+
if (component.props.style) {
127+
measures = getChildrenMeasures(component.props.style, children);
128+
}
80129

81130
return {
82-
left: getTooltipXPosition(children, tooltip),
83-
top: getTooltipYPosition(children, tooltip),
131+
left: getTooltipXPosition(measures, tooltip),
132+
top: getTooltipYPosition(measures, tooltip, headerHeight),
84133
};
85134
};

src/react-navigation/types.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,7 @@ export type MaterialBottomTabNavigationConfig = Partial<
116116
| 'getTestID'
117117
>
118118
>;
119+
120+
export type NavigationParams = {
121+
params?: { headerHeight?: number };
122+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useRoute, RouteProp } from '@react-navigation/native';
2+
3+
import type { NavigationParams } from './types';
4+
5+
/**
6+
* Gets app bar height for current screen
7+
*
8+
* @returns app bar height of parent screen
9+
*/
10+
const useAppBarHeight = (): number => {
11+
const { params } = useRoute<RouteProp<NavigationParams>>();
12+
13+
return params?.headerHeight ?? 0;
14+
};
15+
16+
export default useAppBarHeight;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useNavigation, NavigationProp } from '@react-navigation/native';
2+
3+
import type { NavigationParams } from './types';
4+
5+
/**
6+
* Get properly typed navigation object from useNavigation hook
7+
*
8+
* @returns navigation object
9+
*/
10+
const useInternalNavigation = (): NavigationProp<NavigationParams> =>
11+
useNavigation<NavigationProp<NavigationParams>>();
12+
13+
export default useInternalNavigation;

0 commit comments

Comments
 (0)