Skip to content

Commit e226c20

Browse files
committed
feat: add arrows to popover
1 parent b0bc450 commit e226c20

6 files changed

Lines changed: 414 additions & 47 deletions

File tree

src/components/Popover/Popover.tsx

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,12 @@ import {
66
View,
77
} from 'react-native';
88

9+
import { POSITION, Position } from '../../constants';
910
import { Theme, withTheme } from '../../theme';
1011
import { PopoverStyles } from '../../theme/style-getters/getPopoverStyles';
1112
import Modal from '../Dialog/Modal';
1213
import { LayoutMeasure, LayoutMeasurements } from '../Helpers';
13-
14-
export type Position =
15-
| 'top-left'
16-
| 'top'
17-
| 'top-right'
18-
| 'left'
19-
| 'right'
20-
| 'bottom-right'
21-
| 'bottom'
22-
| 'bottom-left';
14+
import { getPopoverArrow } from './getPopoverArrow';
2315

2416
export interface PopoverProps {
2517
theme: Theme;
@@ -55,26 +47,38 @@ const resolveCorrectPosition = (position: Position) => ({
5547
}) => {
5648
let newPosition = position;
5749

58-
if (position.includes('bottom') && shouldFlipBottomToTop) {
59-
newPosition = newPosition.replace('bottom', 'top') as 'top';
50+
if (position.includes(POSITION.BOTTOM) && shouldFlipBottomToTop) {
51+
newPosition = newPosition.replace(
52+
POSITION.BOTTOM,
53+
POSITION.TOP,
54+
) as Position;
6055
}
6156

62-
if (position.includes('top') && shouldFlipTopToBottom) {
63-
newPosition = newPosition.replace('top', 'bottom') as 'bottom';
57+
if (position.includes(POSITION.TOP) && shouldFlipTopToBottom) {
58+
newPosition = newPosition.replace(
59+
POSITION.TOP,
60+
POSITION.BOTTOM,
61+
) as Position;
6462
}
6563

66-
if (position.includes('left') && shouldFlipLeftToRight) {
67-
newPosition = newPosition.replace('left', 'right') as 'right';
64+
if (position.includes(POSITION.LEFT) && shouldFlipLeftToRight) {
65+
newPosition = newPosition.replace(
66+
POSITION.LEFT,
67+
POSITION.RIGHT,
68+
) as Position;
6869
}
6970

70-
if (position.includes('right') && shouldFlipRightToLeft) {
71-
newPosition = newPosition.replace('right', 'left') as 'left';
71+
if (position.includes(POSITION.RIGHT) && shouldFlipRightToLeft) {
72+
newPosition = newPosition.replace(
73+
POSITION.RIGHT,
74+
POSITION.LEFT,
75+
) as Position;
7276
}
7377

7478
return newPosition;
7579
};
7680

77-
const getPositionCoordinates = (position: Position) => (
81+
const getPopoverPosition = (position: Position) => (
7882
screenLayout: ScaledSize,
7983
) => (targetMeasurements: LayoutMeasurements) => (
8084
popoverMeasurements: LayoutMeasurements,
@@ -87,12 +91,12 @@ const getPositionCoordinates = (position: Position) => (
8791
targetMeasurements.height -
8892
offset,
8993
shouldFlipLeftToRight:
90-
position === 'left'
94+
position === POSITION.LEFT
9195
? popoverMeasurements.width + offset > targetMeasurements.pageX - offset
9296
: popoverMeasurements.width + offset >
9397
screenLayout.width - targetMeasurements.pageX,
9498
shouldFlipRightToLeft:
95-
position === 'right'
99+
position === POSITION.RIGHT
96100
? targetMeasurements.pageX +
97101
targetMeasurements.width +
98102
popoverMeasurements.width +
@@ -105,13 +109,17 @@ const getPositionCoordinates = (position: Position) => (
105109
});
106110

107111
switch (newPosition) {
108-
case 'top-left':
112+
case POSITION.TOP_LEFT:
109113
return {
114+
position: POSITION.TOP_LEFT,
115+
110116
left: targetMeasurements.pageX,
111117
top: targetMeasurements.pageY - popoverMeasurements.height - offset,
112118
};
113-
case 'top':
119+
case POSITION.TOP:
114120
return {
121+
position: POSITION.TOP,
122+
115123
left: targetMeasurements.pageX,
116124
top: targetMeasurements.pageY - popoverMeasurements.height - offset,
117125
transform: [
@@ -121,16 +129,20 @@ const getPositionCoordinates = (position: Position) => (
121129
},
122130
],
123131
};
124-
case 'top-right':
132+
case POSITION.TOP_RIGHT:
125133
return {
134+
position: POSITION.TOP_RIGHT,
135+
126136
left:
127137
targetMeasurements.pageX -
128138
popoverMeasurements.width +
129139
targetMeasurements.width,
130140
top: targetMeasurements.pageY - popoverMeasurements.height - offset,
131141
};
132-
case 'left':
142+
case POSITION.LEFT:
133143
return {
144+
position: POSITION.LEFT,
145+
134146
left: targetMeasurements.pageX - popoverMeasurements.width - offset,
135147
top: targetMeasurements.pageY,
136148
transform: [
@@ -140,8 +152,10 @@ const getPositionCoordinates = (position: Position) => (
140152
},
141153
],
142154
};
143-
case 'right':
155+
case POSITION.RIGHT:
144156
return {
157+
position: POSITION.RIGHT,
158+
145159
left: targetMeasurements.pageX + targetMeasurements.width + offset,
146160
top: targetMeasurements.pageY,
147161
transform: [
@@ -151,16 +165,20 @@ const getPositionCoordinates = (position: Position) => (
151165
},
152166
],
153167
};
154-
case 'bottom-right':
168+
case POSITION.BOTTOM_RIGHT:
155169
return {
170+
position: POSITION.BOTTOM_RIGHT,
171+
156172
left:
157173
targetMeasurements.pageX -
158174
popoverMeasurements.width +
159175
targetMeasurements.width,
160176
top: targetMeasurements.pageY + targetMeasurements.height + offset,
161177
};
162-
case 'bottom':
178+
case POSITION.BOTTOM:
163179
return {
180+
position: POSITION.BOTTOM,
181+
164182
left: targetMeasurements.pageX,
165183
top: targetMeasurements.pageY + targetMeasurements.height + offset,
166184
transform: [
@@ -170,13 +188,17 @@ const getPositionCoordinates = (position: Position) => (
170188
},
171189
],
172190
};
173-
case 'bottom-left':
191+
case POSITION.BOTTOM_LEFT:
174192
return {
193+
position: POSITION.BOTTOM_LEFT,
194+
175195
left: targetMeasurements.pageX,
176196
top: targetMeasurements.pageY + targetMeasurements.height + offset,
177197
};
178198
default:
179-
return {};
199+
return {
200+
position: POSITION.BOTTOM_RIGHT,
201+
};
180202
}
181203
};
182204

@@ -217,31 +239,32 @@ class PopoverBase extends React.Component<PopoverProps, PopoverState> {
217239
parentHeight,
218240
isVisible,
219241
onClose,
220-
position = 'bottom',
242+
position = POSITION.BOTTOM,
221243
offset = 14,
222244
} = this.props;
223245
const { popoverMeasurements, targetMeasurements } = this.state;
224246
const {
225-
containerStyle,
226247
popoverStyle,
227248
modalContainerStyle,
228249
overlayStyle,
229250
} = theme.getPopoverStyles();
230251

231252
const windowDimensions = Dimensions.get('window');
232-
const positionCoordinates = getPositionCoordinates(position)({
253+
const {
254+
position: correctedPosition,
255+
...popoverPositionStyle
256+
} = getPopoverPosition(position)({
233257
...windowDimensions,
234258
height: parentHeight || windowDimensions.height,
235259
})(targetMeasurements)(popoverMeasurements)(offset);
236260

261+
const renderArrow = getPopoverArrow(correctedPosition)(targetMeasurements)(
262+
theme,
263+
);
264+
237265
return (
238266
<>
239267
<LayoutMeasure
240-
style={{
241-
...containerStyle,
242-
...(dangerouslySetInlineStyle &&
243-
dangerouslySetInlineStyle.containerStyle),
244-
}}
245268
onMeasure={measurements =>
246269
this.setState({ targetMeasurements: measurements })
247270
}
@@ -266,7 +289,7 @@ class PopoverBase extends React.Component<PopoverProps, PopoverState> {
266289
...popoverStyle,
267290
...(dangerouslySetInlineStyle &&
268291
dangerouslySetInlineStyle.popoverStyle),
269-
...positionCoordinates,
292+
...popoverPositionStyle,
270293
// Hide flash mis-positioned content
271294
opacity:
272295
popoverMeasurements.width === 0 ||
@@ -279,6 +302,7 @@ class PopoverBase extends React.Component<PopoverProps, PopoverState> {
279302
}
280303
>
281304
{content}
305+
{renderArrow}
282306
</LayoutMeasure>
283307
<TouchableWithoutFeedback
284308
onPress={() => {

0 commit comments

Comments
 (0)