forked from Expensify/App
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgetTooltipStyles.js
204 lines (186 loc) · 10.1 KB
/
getTooltipStyles.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import spacing from './utilities/spacing';
import styles from './styles';
import colors from './colors';
import themeColors from './themes/default';
import fontFamily from './fontFamily';
import variables from './variables';
import roundToNearestMultipleOfFour from './roundToNearestMultipleOfFour';
// This defines the proximity with the edge of the window in which tooltips should not be displayed.
// If a tooltip is too close to the edge of the screen, we'll shift it towards the center.
const GUTTER_WIDTH = variables.gutterWidth;
// The height of a tooltip pointer
const POINTER_HEIGHT = 4;
// The width of a tooltip pointer
const POINTER_WIDTH = 12;
/**
* Compute the amount the tooltip needs to be horizontally shifted in order to keep it from displaying in the gutters.
*
* @param {Number} windowWidth - The width of the window.
* @param {Number} xOffset - The distance between the left edge of the window
* and the left edge of the wrapped component.
* @param {Number} componentWidth - The width of the wrapped component.
* @param {Number} tooltipWidth - The width of the tooltip itself.
* @param {Number} [manualShiftHorizontal] - Any additional amount to manually shift the tooltip to the left or right.
* A positive value shifts it to the right,
* and a negative value shifts it to the left.
* @returns {Number}
*/
function computeHorizontalShift(windowWidth, xOffset, componentWidth, tooltipWidth, manualShiftHorizontal) {
// First find the left and right edges of the tooltip (by default, it is centered on the component).
const componentCenter = xOffset + (componentWidth / 2) + manualShiftHorizontal;
const tooltipLeftEdge = componentCenter - (tooltipWidth / 2);
const tooltipRightEdge = componentCenter + (tooltipWidth / 2);
if (tooltipLeftEdge < GUTTER_WIDTH) {
// Tooltip is in left gutter, shift right by a multiple of four.
return roundToNearestMultipleOfFour(GUTTER_WIDTH - tooltipLeftEdge);
}
if (tooltipRightEdge > (windowWidth - GUTTER_WIDTH)) {
// Tooltip is in right gutter, shift left by a multiple of four.
return roundToNearestMultipleOfFour(windowWidth - GUTTER_WIDTH - tooltipRightEdge);
}
// Tooltip is not in the gutter, so no need to shift it horizontally
return 0;
}
/**
* Generate styles for the tooltip component.
*
* @param {Number} currentSize - The current size of the tooltip used in the scaling animation.
* @param {Number} windowWidth - The width of the window.
* @param {Number} xOffset - The distance between the left edge of the window
* and the left edge of the wrapped component.
* @param {Number} yOffset - The distance between the top edge of the window
* and the top edge of the wrapped component.
* @param {Number} componentWidth - The width of the wrapped component.
* @param {Number} componentHeight - The height of the wrapped component.
* @param {Number} maxWidth - The tooltip's max width.
* @param {Number} tooltipWidth - The width of the tooltip itself.
* @param {Number} tooltipHeight - The height of the tooltip itself.
* @param {Number} tooltipTextWidth - The tooltip's inner text width.
* @param {Number} [manualShiftHorizontal] - Any additional amount to manually shift the tooltip to the left or right.
* A positive value shifts it to the right,
* and a negative value shifts it to the left.
* @param {Number} [manualShiftVertical] - Any additional amount to manually shift the tooltip up or down.
* A positive value shifts it down, and a negative value shifts it up.
* @returns {Object}
*/
export default function getTooltipStyles(
currentSize,
windowWidth,
xOffset,
yOffset,
componentWidth,
componentHeight,
maxWidth,
tooltipWidth,
tooltipHeight,
tooltipTextWidth,
manualShiftHorizontal = 0,
manualShiftVertical = 0,
) {
// Determine if the tooltip should display below the wrapped component.
// If a tooltip will try to render within GUTTER_WIDTH logical pixels of the top of the screen,
// we'll display it beneath its wrapped component rather than above it as usual.
const shouldShowBelow = (yOffset - tooltipHeight) < GUTTER_WIDTH;
// Determine if we need to shift the tooltip horizontally to prevent it
// from displaying too near to the edge of the screen.
const horizontalShift = computeHorizontalShift(windowWidth, xOffset, componentWidth, tooltipWidth, manualShiftHorizontal);
const tooltipVerticalPadding = spacing.pv1;
const tooltipFontSize = variables.fontSizeSmall;
// We get wrapper width based on the tooltip's inner text width so the wrapper is just big enough to fit text and prevent white space.
// If the text width is less than the maximum available width, add horizontal padding.
// Note: tooltipTextWidth ignores the fractions (OffsetWidth) so add 1px to fit the text properly.
const wrapperWidth = tooltipTextWidth && tooltipTextWidth < maxWidth
? tooltipTextWidth + (spacing.ph2.paddingHorizontal * 2) + 1
: maxWidth;
return {
animationStyle: {
// remember Transform causes a new Local cordinate system
// https://drafts.csswg.org/css-transforms-1/#transform-rendering
// so Position fixed children will be relative to this new Local cordinate system
transform: [{
scale: currentSize,
}],
},
tooltipWrapperStyle: {
position: 'fixed',
backgroundColor: themeColors.heading,
borderRadius: variables.componentBorderRadiusSmall,
...tooltipVerticalPadding,
...spacing.ph2,
zIndex: variables.tooltipzIndex,
width: wrapperWidth,
// Because it uses fixed positioning, the top-left corner of the tooltip is aligned
// with the top-left corner of the window by default.
// we will use yOffset to position the tooltip relative to the Wrapped Component
// So we need to shift the tooltip vertically and horizontally to position it correctly.
//
// First, we'll position it vertically.
// To shift the tooltip down, we'll give `top` a positive value.
// To shift the tooltip up, we'll give `top` a negative value.
top: shouldShowBelow
// We need to shift the tooltip down below the component. So shift the tooltip down (+) by...
? (yOffset + componentHeight + POINTER_HEIGHT + manualShiftVertical)
// We need to shift the tooltip up above the component. So shift the tooltip up (-) by...
: ((yOffset - (tooltipHeight + POINTER_HEIGHT)) + manualShiftVertical),
// Next, we'll position it horizontally.
// we will use xOffset to position the tooltip relative to the Wrapped Component
// To shift the tooltip right, we'll give `left` a positive value.
// To shift the tooltip left, we'll give `left` a negative value.
//
// So we'll:
// 1) Shift the tooltip right (+) to the center of the component,
// so the left edge lines up with the component center.
// 2) Shift it left (-) to by half the tooltip's width,
// so the tooltip's center lines up with the center of the wrapped component.
// 3) Add the horizontal shift (left or right) computed above to keep it out of the gutters.
// 4) Lastly, add the manual horizontal shift passed in as a parameter.
left: xOffset + ((componentWidth / 2) - (tooltipWidth / 2)) + horizontalShift + manualShiftHorizontal,
},
tooltipTextStyle: {
color: themeColors.textReversed,
fontFamily: fontFamily.GTA,
fontSize: tooltipFontSize,
overflowWrap: 'normal',
overflow: 'hidden',
whiteSpace: 'nowrap',
},
pointerWrapperStyle: {
position: 'fixed',
// By default, the pointer's top-left will align with the top-left of the wrapped tooltip.
//
// To align it vertically, we'll:
//
// Shift the pointer up (-) by component's height, so that the bottom of the pointer lines up
// with the top of the wrapped component.
//
// OR if it should show below:
//
// Shift the pointer down (+) by the component's height,
// so that the top of the pointer aligns with the bottom of the component.
//
// Always add the manual vertical shift passed in as a parameter.
top: shouldShowBelow ? (manualShiftVertical - POINTER_HEIGHT) : (tooltipHeight + manualShiftVertical),
// To align it horizontally, we'll:
// 1) Shift the pointer to the right (+) by the half the tooltipWidth's width,
// so the left edge of the pointer lines up with the tooltipWidth's center.
// 2) To the left (-) by half the pointer's width,
// so the pointer's center lines up with the tooltipWidth's center.
// 3) Due to the tip start from the left edge of wrapper Tooltip so we have to remove the
// horizontalShift which is added to adjust it into the Window
left: -horizontalShift + ((tooltipWidth / 2) - (POINTER_WIDTH / 2)),
},
pointerStyle: {
width: 0,
height: 0,
backgroundColor: colors.transparent,
borderStyle: 'solid',
borderLeftWidth: POINTER_WIDTH / 2,
borderRightWidth: POINTER_WIDTH / 2,
borderTopWidth: POINTER_HEIGHT,
borderLeftColor: colors.transparent,
borderRightColor: colors.transparent,
borderTopColor: themeColors.heading,
...(shouldShowBelow ? styles.flipUpsideDown : {}),
},
};
}