react-native-ui-lib 7.36.0 → 7.37.0-snapshot.6102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/package.json +2 -2
- package/src/components/floatingButton/index.js +0 -1
- package/src/components/hint/Hint.driver.new.d.ts +19 -0
- package/src/components/hint/Hint.driver.new.js +19 -0
- package/src/components/hint/HintAnchor.d.ts +13 -0
- package/src/components/hint/HintAnchor.js +47 -0
- package/src/components/hint/HintBubble.d.ts +12 -0
- package/src/components/hint/HintBubble.js +64 -0
- package/src/components/hint/HintMockChildren.d.ts +8 -0
- package/src/components/hint/HintMockChildren.js +49 -0
- package/src/components/hint/HintOld.d.ts +196 -0
- package/src/components/hint/HintOld.js +549 -0
- package/src/components/hint/hooks/useHintAccessibility.d.ts +10 -0
- package/src/components/hint/hooks/useHintAccessibility.js +26 -0
- package/src/components/hint/hooks/useHintLayout.d.ts +13 -0
- package/src/components/hint/hooks/useHintLayout.js +66 -0
- package/src/components/hint/hooks/useHintPosition.d.ts +29 -0
- package/src/components/hint/hooks/useHintPosition.js +119 -0
- package/src/components/hint/hooks/useHintVisibility.d.ts +6 -0
- package/src/components/hint/hooks/useHintVisibility.js +26 -0
- package/src/components/hint/index.d.ts +17 -186
- package/src/components/hint/index.js +150 -403
- package/src/components/hint/types.d.ts +106 -0
- package/src/components/hint/types.js +11 -0
- package/src/components/picker/PickerItemsList.js +12 -3
- package/src/components/view/View.driver.new.js +2 -1
- package/src/incubator/expandableOverlay/index.d.ts +1 -0
- package/src/incubator/expandableOverlay/index.js +17 -5
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import _isUndefined from "lodash/isUndefined";
|
|
2
|
+
import _isEqual from "lodash/isEqual";
|
|
3
|
+
import _isString from "lodash/isString";
|
|
4
|
+
import React, { Component, isValidElement } from 'react';
|
|
5
|
+
import { Animated, StyleSheet, AccessibilityInfo, findNodeHandle, TouchableWithoutFeedback } from 'react-native';
|
|
6
|
+
import { Typography, Spacings, Colors, BorderRadiuses, Shadows } from "../../style";
|
|
7
|
+
import { Constants, asBaseComponent } from "../../commons/new";
|
|
8
|
+
import View from "../view";
|
|
9
|
+
import Text from "../text";
|
|
10
|
+
import Image from "../image";
|
|
11
|
+
import Modal from "../modal";
|
|
12
|
+
import TouchableOpacity from "../touchableOpacity";
|
|
13
|
+
const sideTip = require("./assets/hintTipSide.png");
|
|
14
|
+
const middleTip = require("./assets/hintTipMiddle.png");
|
|
15
|
+
const DEFAULT_COLOR = Colors.$backgroundPrimaryHeavy;
|
|
16
|
+
const DEFAULT_HINT_OFFSET = Spacings.s4;
|
|
17
|
+
const DEFAULT_EDGE_MARGINS = Spacings.s5;
|
|
18
|
+
const HINT_MIN_WIDTH = 68;
|
|
19
|
+
var TARGET_POSITIONS = /*#__PURE__*/function (TARGET_POSITIONS) {
|
|
20
|
+
TARGET_POSITIONS["LEFT"] = "left";
|
|
21
|
+
TARGET_POSITIONS["RIGHT"] = "right";
|
|
22
|
+
TARGET_POSITIONS["CENTER"] = "center";
|
|
23
|
+
return TARGET_POSITIONS;
|
|
24
|
+
}(TARGET_POSITIONS || {});
|
|
25
|
+
var HintPositions = /*#__PURE__*/function (HintPositions) {
|
|
26
|
+
HintPositions["TOP"] = "top";
|
|
27
|
+
HintPositions["BOTTOM"] = "bottom";
|
|
28
|
+
return HintPositions;
|
|
29
|
+
}(HintPositions || {}); // TODO: unify with FeatureHighlightFrame
|
|
30
|
+
/**
|
|
31
|
+
* @description: Hint component for displaying a tooltip over wrapped component
|
|
32
|
+
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/HintsScreen.tsx
|
|
33
|
+
* @notes: You can either wrap a component or pass a specific targetFrame
|
|
34
|
+
* @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/Hint/Hint.gif?raw=true
|
|
35
|
+
*/
|
|
36
|
+
class Hint extends Component {
|
|
37
|
+
static displayName = 'Hint';
|
|
38
|
+
static defaultProps = {
|
|
39
|
+
position: HintPositions.BOTTOM,
|
|
40
|
+
useModal: true
|
|
41
|
+
};
|
|
42
|
+
static positions = HintPositions;
|
|
43
|
+
targetRef = null;
|
|
44
|
+
hintRef = React.createRef();
|
|
45
|
+
animationDuration = 170;
|
|
46
|
+
state = {
|
|
47
|
+
targetLayoutInWindow: undefined,
|
|
48
|
+
targetLayout: this.props.targetFrame,
|
|
49
|
+
hintUnmounted: !this.props.visible,
|
|
50
|
+
hintMessageWidth: undefined
|
|
51
|
+
};
|
|
52
|
+
visibleAnimated = new Animated.Value(Number(!!this.props.visible));
|
|
53
|
+
componentDidMount() {
|
|
54
|
+
this.focusAccessibilityOnHint();
|
|
55
|
+
}
|
|
56
|
+
componentDidUpdate(prevProps) {
|
|
57
|
+
if (prevProps.visible !== this.props.visible) {
|
|
58
|
+
this.animateHint();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
animateHint = () => {
|
|
62
|
+
Animated.timing(this.visibleAnimated, {
|
|
63
|
+
toValue: Number(!!this.props.visible),
|
|
64
|
+
duration: this.animationDuration,
|
|
65
|
+
useNativeDriver: true
|
|
66
|
+
}).start(this.toggleAnimationEndedToRemoveHint);
|
|
67
|
+
};
|
|
68
|
+
toggleAnimationEndedToRemoveHint = () => {
|
|
69
|
+
this.setState({
|
|
70
|
+
hintUnmounted: !this.props.visible
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
focusAccessibilityOnHint = () => {
|
|
74
|
+
const {
|
|
75
|
+
message
|
|
76
|
+
} = this.props;
|
|
77
|
+
const targetRefTag = findNodeHandle(this.targetRef);
|
|
78
|
+
const hintRefTag = findNodeHandle(this.hintRef.current);
|
|
79
|
+
if (targetRefTag && _isString(message)) {
|
|
80
|
+
AccessibilityInfo.setAccessibilityFocus(targetRefTag);
|
|
81
|
+
} else if (hintRefTag) {
|
|
82
|
+
AccessibilityInfo.setAccessibilityFocus(hintRefTag);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
setTargetRef = ref => {
|
|
86
|
+
this.targetRef = ref;
|
|
87
|
+
this.focusAccessibilityOnHint();
|
|
88
|
+
};
|
|
89
|
+
setHintLayout = ({
|
|
90
|
+
nativeEvent: {
|
|
91
|
+
layout
|
|
92
|
+
}
|
|
93
|
+
}) => {
|
|
94
|
+
if (!this.state.hintMessageWidth) {
|
|
95
|
+
this.setState({
|
|
96
|
+
hintMessageWidth: layout.width
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
onTargetLayout = ({
|
|
101
|
+
nativeEvent: {
|
|
102
|
+
layout
|
|
103
|
+
}
|
|
104
|
+
}) => {
|
|
105
|
+
if (!_isEqual(this.state.targetLayout, layout)) {
|
|
106
|
+
this.setState({
|
|
107
|
+
targetLayout: layout
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (!this.state.targetLayoutInWindow || this.props.onBackgroundPress) {
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
this.targetRef?.measureInWindow?.((x, y, width, height) => {
|
|
113
|
+
const targetLayoutInWindow = {
|
|
114
|
+
x,
|
|
115
|
+
y,
|
|
116
|
+
width,
|
|
117
|
+
height
|
|
118
|
+
};
|
|
119
|
+
this.setState({
|
|
120
|
+
targetLayoutInWindow
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
getAccessibilityInfo() {
|
|
127
|
+
const {
|
|
128
|
+
visible,
|
|
129
|
+
message
|
|
130
|
+
} = this.props;
|
|
131
|
+
if (visible && _isString(message)) {
|
|
132
|
+
return {
|
|
133
|
+
accessible: true,
|
|
134
|
+
accessibilityLabel: `hint: ${message}`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
get containerWidth() {
|
|
139
|
+
const {
|
|
140
|
+
containerWidth = Constants.windowWidth
|
|
141
|
+
} = this.props;
|
|
142
|
+
return containerWidth;
|
|
143
|
+
}
|
|
144
|
+
get targetLayout() {
|
|
145
|
+
const {
|
|
146
|
+
onBackgroundPress,
|
|
147
|
+
useModal,
|
|
148
|
+
targetFrame
|
|
149
|
+
} = this.props;
|
|
150
|
+
const {
|
|
151
|
+
targetLayout,
|
|
152
|
+
targetLayoutInWindow
|
|
153
|
+
} = this.state;
|
|
154
|
+
if (targetFrame) {
|
|
155
|
+
return targetFrame;
|
|
156
|
+
}
|
|
157
|
+
return onBackgroundPress && useModal ? targetLayoutInWindow : targetLayout;
|
|
158
|
+
}
|
|
159
|
+
get showHint() {
|
|
160
|
+
return !!this.targetLayout;
|
|
161
|
+
}
|
|
162
|
+
get tipSize() {
|
|
163
|
+
return this.shouldUseSideTip ? {
|
|
164
|
+
width: 14,
|
|
165
|
+
height: 7
|
|
166
|
+
} : {
|
|
167
|
+
width: 20,
|
|
168
|
+
height: 7
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
get hintOffset() {
|
|
172
|
+
const {
|
|
173
|
+
offset = DEFAULT_HINT_OFFSET
|
|
174
|
+
} = this.props;
|
|
175
|
+
return offset;
|
|
176
|
+
}
|
|
177
|
+
get edgeMargins() {
|
|
178
|
+
const {
|
|
179
|
+
edgeMargins = this.isUsingModal() ? DEFAULT_EDGE_MARGINS : 0
|
|
180
|
+
} = this.props;
|
|
181
|
+
return edgeMargins;
|
|
182
|
+
}
|
|
183
|
+
get shouldUseSideTip() {
|
|
184
|
+
const {
|
|
185
|
+
useSideTip
|
|
186
|
+
} = this.props;
|
|
187
|
+
if (!_isUndefined(useSideTip)) {
|
|
188
|
+
return useSideTip;
|
|
189
|
+
}
|
|
190
|
+
return this.getTargetPositionOnScreen() !== TARGET_POSITIONS.CENTER;
|
|
191
|
+
}
|
|
192
|
+
get isShortMessage() {
|
|
193
|
+
const {
|
|
194
|
+
hintMessageWidth
|
|
195
|
+
} = this.state;
|
|
196
|
+
return hintMessageWidth && hintMessageWidth < Constants.screenWidth / 2;
|
|
197
|
+
}
|
|
198
|
+
getTargetPositionOnScreen() {
|
|
199
|
+
if (this.targetLayout?.x !== undefined && this.targetLayout?.width) {
|
|
200
|
+
const targetMidPosition = this.targetLayout.x + this.targetLayout.width / 2;
|
|
201
|
+
if (targetMidPosition > this.containerWidth * (4 / 5)) {
|
|
202
|
+
return TARGET_POSITIONS.RIGHT;
|
|
203
|
+
} else if (targetMidPosition < this.containerWidth * (1 / 5)) {
|
|
204
|
+
return TARGET_POSITIONS.LEFT;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return TARGET_POSITIONS.CENTER;
|
|
208
|
+
}
|
|
209
|
+
getContainerPosition() {
|
|
210
|
+
if (this.targetLayout) {
|
|
211
|
+
return {
|
|
212
|
+
top: this.targetLayout.y,
|
|
213
|
+
left: this.targetLayout.x
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
getHintPosition() {
|
|
218
|
+
const {
|
|
219
|
+
position
|
|
220
|
+
} = this.props;
|
|
221
|
+
const hintPositionStyle = {
|
|
222
|
+
alignItems: 'center'
|
|
223
|
+
};
|
|
224
|
+
if (this.targetLayout?.x !== undefined) {
|
|
225
|
+
hintPositionStyle.left = -this.targetLayout.x;
|
|
226
|
+
}
|
|
227
|
+
if (position === HintPositions.TOP) {
|
|
228
|
+
hintPositionStyle.bottom = 0;
|
|
229
|
+
} else if (this.targetLayout?.height) {
|
|
230
|
+
hintPositionStyle.top = this.targetLayout.height;
|
|
231
|
+
}
|
|
232
|
+
const targetPositionOnScreen = this.getTargetPositionOnScreen();
|
|
233
|
+
if (targetPositionOnScreen === TARGET_POSITIONS.RIGHT) {
|
|
234
|
+
hintPositionStyle.alignItems = Constants.isRTL ? 'flex-start' : 'flex-end';
|
|
235
|
+
} else if (targetPositionOnScreen === TARGET_POSITIONS.LEFT) {
|
|
236
|
+
hintPositionStyle.alignItems = Constants.isRTL ? 'flex-end' : 'flex-start';
|
|
237
|
+
}
|
|
238
|
+
return hintPositionStyle;
|
|
239
|
+
}
|
|
240
|
+
getHintPadding() {
|
|
241
|
+
const paddings = {
|
|
242
|
+
paddingVertical: this.hintOffset,
|
|
243
|
+
paddingHorizontal: this.edgeMargins
|
|
244
|
+
};
|
|
245
|
+
if (this.shouldUseSideTip && this.targetLayout?.x !== undefined) {
|
|
246
|
+
const targetPositionOnScreen = this.getTargetPositionOnScreen();
|
|
247
|
+
if (targetPositionOnScreen === TARGET_POSITIONS.LEFT) {
|
|
248
|
+
paddings.paddingLeft = this.targetLayout.x;
|
|
249
|
+
} else if (targetPositionOnScreen === TARGET_POSITIONS.RIGHT && this.targetLayout?.width) {
|
|
250
|
+
paddings.paddingRight = this.containerWidth - this.targetLayout.x - this.targetLayout.width;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return paddings;
|
|
254
|
+
}
|
|
255
|
+
getHintAnimatedStyle = () => {
|
|
256
|
+
const {
|
|
257
|
+
position
|
|
258
|
+
} = this.props;
|
|
259
|
+
const translateY = position === HintPositions.TOP ? -10 : 10;
|
|
260
|
+
return {
|
|
261
|
+
opacity: this.visibleAnimated,
|
|
262
|
+
transform: [{
|
|
263
|
+
translateY: this.visibleAnimated.interpolate({
|
|
264
|
+
inputRange: [0, 1],
|
|
265
|
+
outputRange: [translateY, 0]
|
|
266
|
+
})
|
|
267
|
+
}]
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
getTipPosition() {
|
|
271
|
+
const {
|
|
272
|
+
position
|
|
273
|
+
} = this.props;
|
|
274
|
+
const tipPositionStyle = {};
|
|
275
|
+
if (position === HintPositions.TOP) {
|
|
276
|
+
tipPositionStyle.bottom = this.hintOffset - this.tipSize.height;
|
|
277
|
+
!this.shouldUseSideTip ? tipPositionStyle.bottom += 1 : undefined;
|
|
278
|
+
} else {
|
|
279
|
+
tipPositionStyle.top = this.hintOffset - this.tipSize.height;
|
|
280
|
+
}
|
|
281
|
+
const layoutWidth = this.targetLayout?.width || 0;
|
|
282
|
+
if (this.targetLayout?.x !== undefined) {
|
|
283
|
+
const targetMidWidth = layoutWidth / 2;
|
|
284
|
+
const tipMidWidth = this.tipSize.width / 2;
|
|
285
|
+
const leftPosition = this.shouldUseSideTip ? this.targetLayout.x : this.targetLayout.x + targetMidWidth - tipMidWidth;
|
|
286
|
+
const rightPosition = this.shouldUseSideTip ? this.containerWidth - this.targetLayout.x - layoutWidth : this.containerWidth - this.targetLayout.x - targetMidWidth - tipMidWidth;
|
|
287
|
+
const targetPositionOnScreen = this.getTargetPositionOnScreen();
|
|
288
|
+
switch (targetPositionOnScreen) {
|
|
289
|
+
case TARGET_POSITIONS.LEFT:
|
|
290
|
+
tipPositionStyle.left = Constants.isRTL ? rightPosition : leftPosition;
|
|
291
|
+
break;
|
|
292
|
+
case TARGET_POSITIONS.RIGHT:
|
|
293
|
+
tipPositionStyle.right = Constants.isRTL ? leftPosition : rightPosition;
|
|
294
|
+
break;
|
|
295
|
+
case TARGET_POSITIONS.CENTER:
|
|
296
|
+
default:
|
|
297
|
+
tipPositionStyle.left = this.targetLayout.x + targetMidWidth - tipMidWidth;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return tipPositionStyle;
|
|
302
|
+
}
|
|
303
|
+
getHintOffsetForShortMessage = () => {
|
|
304
|
+
const {
|
|
305
|
+
hintMessageWidth = 0
|
|
306
|
+
} = this.state;
|
|
307
|
+
let hintMessageOffset = 0;
|
|
308
|
+
if (this.isShortMessage) {
|
|
309
|
+
const targetPosition = this.getTipPosition();
|
|
310
|
+
if (targetPosition?.right) {
|
|
311
|
+
hintMessageOffset = -targetPosition?.right + hintMessageWidth / 2;
|
|
312
|
+
}
|
|
313
|
+
if (targetPosition?.left) {
|
|
314
|
+
hintMessageOffset = targetPosition?.left;
|
|
315
|
+
if (this.getTargetPositionOnScreen() === TARGET_POSITIONS.CENTER) {
|
|
316
|
+
hintMessageOffset -= Constants.screenWidth / 2;
|
|
317
|
+
} else {
|
|
318
|
+
hintMessageOffset -= hintMessageWidth / 2;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return hintMessageOffset;
|
|
323
|
+
};
|
|
324
|
+
isUsingModal = () => {
|
|
325
|
+
const {
|
|
326
|
+
onBackgroundPress,
|
|
327
|
+
useModal
|
|
328
|
+
} = this.props;
|
|
329
|
+
return onBackgroundPress && useModal;
|
|
330
|
+
};
|
|
331
|
+
renderOverlay() {
|
|
332
|
+
const {
|
|
333
|
+
targetLayoutInWindow
|
|
334
|
+
} = this.state;
|
|
335
|
+
const {
|
|
336
|
+
onBackgroundPress,
|
|
337
|
+
backdropColor,
|
|
338
|
+
testID
|
|
339
|
+
} = this.props;
|
|
340
|
+
if (targetLayoutInWindow) {
|
|
341
|
+
const containerPosition = this.getContainerPosition();
|
|
342
|
+
return <Animated.View style={[styles.overlay, {
|
|
343
|
+
top: containerPosition.top - targetLayoutInWindow.y,
|
|
344
|
+
left: containerPosition.left - targetLayoutInWindow.x,
|
|
345
|
+
backgroundColor: backdropColor,
|
|
346
|
+
opacity: this.visibleAnimated
|
|
347
|
+
}]} pointerEvents="box-none" testID={`${testID}.overlay`}>
|
|
348
|
+
{onBackgroundPress && <TouchableWithoutFeedback style={StyleSheet.absoluteFillObject} onPress={onBackgroundPress}>
|
|
349
|
+
<View flex />
|
|
350
|
+
</TouchableWithoutFeedback>}
|
|
351
|
+
</Animated.View>;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
renderHintTip() {
|
|
355
|
+
const {
|
|
356
|
+
position,
|
|
357
|
+
color = DEFAULT_COLOR
|
|
358
|
+
} = this.props;
|
|
359
|
+
const source = this.shouldUseSideTip ? sideTip : middleTip;
|
|
360
|
+
const flipVertically = position === HintPositions.TOP;
|
|
361
|
+
const flipHorizontally = this.getTargetPositionOnScreen() === TARGET_POSITIONS.RIGHT;
|
|
362
|
+
const flipStyle = {
|
|
363
|
+
transform: [{
|
|
364
|
+
scaleY: flipVertically ? -1 : 1
|
|
365
|
+
}, {
|
|
366
|
+
scaleX: flipHorizontally ? -1 : 1
|
|
367
|
+
}]
|
|
368
|
+
};
|
|
369
|
+
return <Image tintColor={color} source={source} style={[styles.hintTip, this.getTipPosition(), flipStyle]} />;
|
|
370
|
+
}
|
|
371
|
+
renderHint() {
|
|
372
|
+
const {
|
|
373
|
+
message,
|
|
374
|
+
messageStyle,
|
|
375
|
+
icon,
|
|
376
|
+
iconStyle,
|
|
377
|
+
borderRadius,
|
|
378
|
+
color = DEFAULT_COLOR,
|
|
379
|
+
customContent,
|
|
380
|
+
removePaddings,
|
|
381
|
+
enableShadow,
|
|
382
|
+
visible,
|
|
383
|
+
testID
|
|
384
|
+
} = this.props;
|
|
385
|
+
const hintMessageOffset = this.getHintOffsetForShortMessage();
|
|
386
|
+
return <View testID={`${testID}.message`} row centerV centerH={!!hintMessageOffset} style={[styles.hint, !removePaddings && styles.hintPaddings, visible && enableShadow && styles.containerShadow, {
|
|
387
|
+
backgroundColor: color
|
|
388
|
+
}, !_isUndefined(borderRadius) && {
|
|
389
|
+
borderRadius
|
|
390
|
+
}, hintMessageOffset ? {
|
|
391
|
+
left: hintMessageOffset
|
|
392
|
+
} : undefined]} onLayout={this.setHintLayout} ref={this.hintRef}>
|
|
393
|
+
{customContent}
|
|
394
|
+
{!customContent && icon && <Image source={icon} style={[styles.icon, iconStyle]} />}
|
|
395
|
+
{!customContent && <Text recorderTag={'unmask'} style={[styles.hintMessage, messageStyle]} testID={`${testID}.message.text`}>
|
|
396
|
+
{message}
|
|
397
|
+
</Text>}
|
|
398
|
+
</View>;
|
|
399
|
+
}
|
|
400
|
+
renderHintContainer() {
|
|
401
|
+
const {
|
|
402
|
+
onPress,
|
|
403
|
+
testID
|
|
404
|
+
} = this.props;
|
|
405
|
+
const opacity = onPress ? 0.9 : 1.0;
|
|
406
|
+
if (this.showHint) {
|
|
407
|
+
return <View animated style={[{
|
|
408
|
+
width: this.containerWidth
|
|
409
|
+
}, styles.animatedContainer, this.getHintPosition(), this.getHintPadding(), this.getHintAnimatedStyle()]} pointerEvents="box-none" testID={testID}>
|
|
410
|
+
<TouchableOpacity activeOpacity={opacity} onPress={onPress}>
|
|
411
|
+
{this.renderHint()}
|
|
412
|
+
</TouchableOpacity>
|
|
413
|
+
{this.renderHintTip()}
|
|
414
|
+
</View>;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
renderHintAnchor() {
|
|
418
|
+
const {
|
|
419
|
+
style,
|
|
420
|
+
...others
|
|
421
|
+
} = this.props;
|
|
422
|
+
return <View {...others}
|
|
423
|
+
// this view must be collapsable, don't pass testID or backgroundColor etc'.
|
|
424
|
+
collapsable testID={undefined} style={[styles.container, style, this.getContainerPosition(), !this.isUsingModal() && styles.overlayContainer]}>
|
|
425
|
+
{this.renderHintContainer()}
|
|
426
|
+
</View>;
|
|
427
|
+
}
|
|
428
|
+
renderMockChildren() {
|
|
429
|
+
const {
|
|
430
|
+
children,
|
|
431
|
+
backdropColor
|
|
432
|
+
} = this.props;
|
|
433
|
+
const isBackdropColorPassed = backdropColor !== undefined;
|
|
434
|
+
if (children && React.isValidElement(children)) {
|
|
435
|
+
const layout = {
|
|
436
|
+
...this.getContainerPosition(),
|
|
437
|
+
width: this.targetLayout?.width,
|
|
438
|
+
height: this.targetLayout?.height,
|
|
439
|
+
right: Constants.isRTL ? this.targetLayout?.x : undefined,
|
|
440
|
+
left: Constants.isRTL ? undefined : this.targetLayout?.x
|
|
441
|
+
};
|
|
442
|
+
return <View style={[styles.mockChildrenContainer, layout, !isBackdropColorPassed && styles.hidden]}>
|
|
443
|
+
{React.cloneElement(children, {
|
|
444
|
+
collapsable: false,
|
|
445
|
+
key: 'mock',
|
|
446
|
+
style: [children.props.style, styles.mockChildren]
|
|
447
|
+
})}
|
|
448
|
+
</View>;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
renderChildren() {
|
|
452
|
+
const {
|
|
453
|
+
targetFrame
|
|
454
|
+
} = this.props;
|
|
455
|
+
if (!targetFrame && isValidElement(this.props.children)) {
|
|
456
|
+
return React.cloneElement(this.props.children, {
|
|
457
|
+
key: 'clone',
|
|
458
|
+
collapsable: false,
|
|
459
|
+
onLayout: this.onTargetLayout,
|
|
460
|
+
ref: this.setTargetRef,
|
|
461
|
+
...this.getAccessibilityInfo()
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
render() {
|
|
466
|
+
const {
|
|
467
|
+
onBackgroundPress,
|
|
468
|
+
backdropColor,
|
|
469
|
+
testID
|
|
470
|
+
} = this.props;
|
|
471
|
+
if (!this.props.visible && this.state.hintUnmounted) {
|
|
472
|
+
return this.props.children || null;
|
|
473
|
+
}
|
|
474
|
+
return <>
|
|
475
|
+
{this.renderChildren()}
|
|
476
|
+
{this.isUsingModal() ? <Modal visible={this.showHint} animationType={backdropColor ? 'fade' : 'none'} overlayBackgroundColor={backdropColor} transparent onBackgroundPress={onBackgroundPress} onRequestClose={onBackgroundPress} testID={`${testID}.modal`}>
|
|
477
|
+
{this.renderMockChildren()}
|
|
478
|
+
{this.renderHintAnchor()}
|
|
479
|
+
</Modal> : <>
|
|
480
|
+
{this.renderOverlay()}
|
|
481
|
+
{this.renderMockChildren()}
|
|
482
|
+
{this.renderHintAnchor()}
|
|
483
|
+
</>}
|
|
484
|
+
</>;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const styles = StyleSheet.create({
|
|
488
|
+
container: {
|
|
489
|
+
position: 'absolute'
|
|
490
|
+
},
|
|
491
|
+
hidden: {
|
|
492
|
+
opacity: 0
|
|
493
|
+
},
|
|
494
|
+
overlayContainer: {
|
|
495
|
+
zIndex: 10,
|
|
496
|
+
elevation: 10
|
|
497
|
+
},
|
|
498
|
+
mockChildrenContainer: {
|
|
499
|
+
position: 'absolute'
|
|
500
|
+
},
|
|
501
|
+
mockChildren: {
|
|
502
|
+
margin: undefined,
|
|
503
|
+
marginVertical: undefined,
|
|
504
|
+
marginHorizontal: undefined,
|
|
505
|
+
marginTop: undefined,
|
|
506
|
+
marginRight: undefined,
|
|
507
|
+
marginBottom: undefined,
|
|
508
|
+
marginLeft: undefined,
|
|
509
|
+
top: undefined,
|
|
510
|
+
left: undefined,
|
|
511
|
+
right: undefined,
|
|
512
|
+
bottom: undefined
|
|
513
|
+
},
|
|
514
|
+
overlay: {
|
|
515
|
+
position: 'absolute',
|
|
516
|
+
width: Constants.windowWidth,
|
|
517
|
+
height: Constants.screenHeight
|
|
518
|
+
},
|
|
519
|
+
animatedContainer: {
|
|
520
|
+
position: 'absolute'
|
|
521
|
+
},
|
|
522
|
+
hintTip: {
|
|
523
|
+
position: 'absolute'
|
|
524
|
+
},
|
|
525
|
+
hint: {
|
|
526
|
+
minWidth: HINT_MIN_WIDTH,
|
|
527
|
+
maxWidth: Math.min(Constants.windowWidth - 2 * Spacings.s4, 400),
|
|
528
|
+
borderRadius: BorderRadiuses.br60,
|
|
529
|
+
backgroundColor: DEFAULT_COLOR
|
|
530
|
+
},
|
|
531
|
+
hintPaddings: {
|
|
532
|
+
paddingHorizontal: Spacings.s5,
|
|
533
|
+
paddingTop: Spacings.s3,
|
|
534
|
+
paddingBottom: Spacings.s4
|
|
535
|
+
},
|
|
536
|
+
containerShadow: {
|
|
537
|
+
...Shadows.sh30.bottom
|
|
538
|
+
},
|
|
539
|
+
hintMessage: {
|
|
540
|
+
...Typography.text70,
|
|
541
|
+
color: Colors.white,
|
|
542
|
+
flexShrink: 1
|
|
543
|
+
},
|
|
544
|
+
icon: {
|
|
545
|
+
marginRight: Spacings.s4,
|
|
546
|
+
tintColor: Colors.white
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
export default asBaseComponent(Hint);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ElementRef } from 'react';
|
|
2
|
+
import { View as RNView } from 'react-native';
|
|
3
|
+
import { HintProps } from '../types';
|
|
4
|
+
export default function useHintAccessibility(message?: HintProps['message']): {
|
|
5
|
+
focusAccessibilityOnHint: (targetRef: ElementRef<typeof RNView>, hintRef: ElementRef<typeof RNView>) => void;
|
|
6
|
+
accessibilityInfo: {
|
|
7
|
+
accessible: boolean;
|
|
8
|
+
accessibilityLabel: string;
|
|
9
|
+
} | undefined;
|
|
10
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import _isString from "lodash/isString";
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
import { AccessibilityInfo, findNodeHandle } from 'react-native';
|
|
4
|
+
export default function useHintAccessibility(message) {
|
|
5
|
+
const focusAccessibilityOnHint = useCallback((targetRef, hintRef) => {
|
|
6
|
+
const targetRefTag = findNodeHandle(targetRef);
|
|
7
|
+
const hintRefTag = findNodeHandle(hintRef);
|
|
8
|
+
if (targetRefTag && _isString(message)) {
|
|
9
|
+
AccessibilityInfo.setAccessibilityFocus(targetRefTag);
|
|
10
|
+
} else if (hintRefTag) {
|
|
11
|
+
AccessibilityInfo.setAccessibilityFocus(hintRefTag);
|
|
12
|
+
}
|
|
13
|
+
}, [message]);
|
|
14
|
+
const accessibilityInfo = useMemo(() => {
|
|
15
|
+
if (_isString(message)) {
|
|
16
|
+
return {
|
|
17
|
+
accessible: true,
|
|
18
|
+
accessibilityLabel: `hint: ${message}`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}, [message]);
|
|
22
|
+
return {
|
|
23
|
+
focusAccessibilityOnHint,
|
|
24
|
+
accessibilityInfo
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type { LayoutChangeEvent, LayoutRectangle, View as RNView } from 'react-native';
|
|
3
|
+
import { HintProps } from '../types';
|
|
4
|
+
type UseHintLayoutProps = Pick<HintProps, 'onBackgroundPress' | 'targetFrame'>;
|
|
5
|
+
export default function useHintLayout({ onBackgroundPress, targetFrame }: UseHintLayoutProps): {
|
|
6
|
+
targetLayoutState: LayoutRectangle | undefined;
|
|
7
|
+
targetLayoutInWindowState: LayoutRectangle | undefined;
|
|
8
|
+
hintMessageWidth: number | undefined;
|
|
9
|
+
targetRef: import("react").MutableRefObject<RNView | null>;
|
|
10
|
+
onTargetLayout: ({ nativeEvent: { layout } }: LayoutChangeEvent) => void;
|
|
11
|
+
setHintLayout: ({ nativeEvent: { layout } }: LayoutChangeEvent) => void;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import _isEqual from "lodash/isEqual";
|
|
2
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
3
|
+
import { SafeAreaInsetsManager } from 'uilib-native';
|
|
4
|
+
import { Constants } from "../../../commons/new";
|
|
5
|
+
export default function useHintLayout({
|
|
6
|
+
onBackgroundPress,
|
|
7
|
+
targetFrame
|
|
8
|
+
}) {
|
|
9
|
+
const [targetLayoutState, setTargetLayout] = useState(targetFrame);
|
|
10
|
+
const [targetLayoutInWindowState, setTargetLayoutInWindow] = useState();
|
|
11
|
+
const [hintMessageWidth, setHintMessageWidth] = useState();
|
|
12
|
+
const targetRef = useRef(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (targetFrame) {
|
|
15
|
+
if (!onBackgroundPress) {
|
|
16
|
+
setTargetLayoutInWindow(targetFrame);
|
|
17
|
+
} else {
|
|
18
|
+
SafeAreaInsetsManager.getSafeAreaInsets().then(insets => {
|
|
19
|
+
setTargetLayoutInWindow({
|
|
20
|
+
...targetFrame,
|
|
21
|
+
y: targetFrame.y + (insets?.top ?? 0) + Constants.getSafeAreaInsets().top
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
const onTargetLayout = useCallback(({
|
|
28
|
+
nativeEvent: {
|
|
29
|
+
layout
|
|
30
|
+
}
|
|
31
|
+
}) => {
|
|
32
|
+
if (!_isEqual(targetLayoutState, layout)) {
|
|
33
|
+
setTargetLayout(layout);
|
|
34
|
+
}
|
|
35
|
+
if (!targetLayoutInWindowState || !!onBackgroundPress) {
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
targetRef?.current?.measureInWindow?.((x, y, width, height) => {
|
|
38
|
+
const targetLayoutInWindow = {
|
|
39
|
+
x,
|
|
40
|
+
y,
|
|
41
|
+
width,
|
|
42
|
+
height
|
|
43
|
+
};
|
|
44
|
+
setTargetLayoutInWindow(targetLayoutInWindow);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}, [targetLayoutState, targetLayoutInWindowState, onBackgroundPress]);
|
|
49
|
+
const setHintLayout = useCallback(({
|
|
50
|
+
nativeEvent: {
|
|
51
|
+
layout
|
|
52
|
+
}
|
|
53
|
+
}) => {
|
|
54
|
+
if (!hintMessageWidth) {
|
|
55
|
+
setHintMessageWidth(layout.width);
|
|
56
|
+
}
|
|
57
|
+
}, [hintMessageWidth]);
|
|
58
|
+
return {
|
|
59
|
+
targetLayoutState,
|
|
60
|
+
targetLayoutInWindowState,
|
|
61
|
+
hintMessageWidth,
|
|
62
|
+
targetRef,
|
|
63
|
+
onTargetLayout,
|
|
64
|
+
setHintLayout
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LayoutRectangle } from 'react-native';
|
|
2
|
+
import { LayoutStyle, PositionStyle, PaddingsStyle, TargetAlignments, HintProps } from '../types';
|
|
3
|
+
interface UseHintPositionProps extends Pick<HintProps, 'position' | 'useSideTip'> {
|
|
4
|
+
isUsingModal: boolean;
|
|
5
|
+
targetLayoutState?: LayoutRectangle;
|
|
6
|
+
targetLayoutInWindowState?: LayoutRectangle;
|
|
7
|
+
containerWidth: number;
|
|
8
|
+
edgeMargins: number;
|
|
9
|
+
offset: number;
|
|
10
|
+
hintMessageWidth?: number;
|
|
11
|
+
}
|
|
12
|
+
export default function useHintPosition({ isUsingModal, targetLayoutState, targetLayoutInWindowState, position, containerWidth, useSideTip, offset, edgeMargins, hintMessageWidth }: UseHintPositionProps): {
|
|
13
|
+
tipSize: {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
};
|
|
17
|
+
targetAlignmentOnScreen: TargetAlignments;
|
|
18
|
+
hintContainerLayout: LayoutStyle;
|
|
19
|
+
tipPositionStyle: PositionStyle;
|
|
20
|
+
hintPadding: PaddingsStyle;
|
|
21
|
+
hintPositionStyle: {
|
|
22
|
+
left: number;
|
|
23
|
+
};
|
|
24
|
+
targetScreenToRelativeOffset: {
|
|
25
|
+
left: number;
|
|
26
|
+
top: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export {};
|