@momo-kits/animated-tooltip 0.153.2
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/TooltipButtons.tsx +67 -0
- package/index.tsx +410 -0
- package/package.json +17 -0
- package/publish.sh +18 -0
- package/styles.ts +45 -0
- package/types.ts +77 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
import { TouchableOpacity } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
ButtonProps,
|
|
6
|
+
Colors,
|
|
7
|
+
Icon,
|
|
8
|
+
IconButton as FoundationIconButton,
|
|
9
|
+
IconButtonProps,
|
|
10
|
+
Radius,
|
|
11
|
+
Spacing,
|
|
12
|
+
Text,
|
|
13
|
+
} from '@momo-kits/foundation';
|
|
14
|
+
|
|
15
|
+
const PrimaryButton: FC<ButtonProps> = ({ title, onPress }) => {
|
|
16
|
+
return (
|
|
17
|
+
<Button
|
|
18
|
+
full={false}
|
|
19
|
+
title={title}
|
|
20
|
+
onPress={onPress}
|
|
21
|
+
type={'secondary'}
|
|
22
|
+
size={'medium'}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const SecondaryButton: FC<ButtonProps> = ({ title, onPress, style }) => {
|
|
28
|
+
return (
|
|
29
|
+
<TouchableOpacity
|
|
30
|
+
style={[
|
|
31
|
+
{
|
|
32
|
+
paddingHorizontal: Spacing.M,
|
|
33
|
+
paddingVertical: Spacing.S,
|
|
34
|
+
},
|
|
35
|
+
style,
|
|
36
|
+
]}
|
|
37
|
+
onPress={onPress}
|
|
38
|
+
>
|
|
39
|
+
<Text color={Colors.black_01} typography={'action_s_bold'}>
|
|
40
|
+
{title}
|
|
41
|
+
</Text>
|
|
42
|
+
</TouchableOpacity>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const IconButton: FC<IconButtonProps> = ({ icon, onPress, style }) => {
|
|
47
|
+
return (
|
|
48
|
+
<TouchableOpacity
|
|
49
|
+
onPress={onPress}
|
|
50
|
+
style={[
|
|
51
|
+
{
|
|
52
|
+
width: 36,
|
|
53
|
+
height: 36,
|
|
54
|
+
borderRadius: Radius.XL,
|
|
55
|
+
backgroundColor: Colors.black_01,
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
justifyContent: 'center',
|
|
58
|
+
},
|
|
59
|
+
style,
|
|
60
|
+
]}
|
|
61
|
+
>
|
|
62
|
+
<Icon source={icon} />
|
|
63
|
+
</TouchableOpacity>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export { PrimaryButton, SecondaryButton, IconButton };
|
package/index.tsx
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { Animated, Easing, View, ViewStyle } from 'react-native';
|
|
11
|
+
import {
|
|
12
|
+
Colors,
|
|
13
|
+
Icon,
|
|
14
|
+
MiniAppContext,
|
|
15
|
+
ScreenContext,
|
|
16
|
+
Spacing,
|
|
17
|
+
Text,
|
|
18
|
+
} from '@momo-kits/foundation';
|
|
19
|
+
import styles from './styles';
|
|
20
|
+
import { TooltipPlacement, TooltipProps, TooltipRef } from './types';
|
|
21
|
+
import { IconButton, PrimaryButton, SecondaryButton } from './TooltipButtons';
|
|
22
|
+
|
|
23
|
+
const Tooltip = forwardRef<TooltipRef, TooltipProps>(function Tooltip(
|
|
24
|
+
{
|
|
25
|
+
children,
|
|
26
|
+
title,
|
|
27
|
+
description,
|
|
28
|
+
buttons = [],
|
|
29
|
+
placement = 'top',
|
|
30
|
+
align = 'center',
|
|
31
|
+
visible,
|
|
32
|
+
showDelay = 120,
|
|
33
|
+
hideDelay = 120,
|
|
34
|
+
animationDuration = 180,
|
|
35
|
+
offset = 8,
|
|
36
|
+
accessibilityLabel,
|
|
37
|
+
onVisibleChange,
|
|
38
|
+
},
|
|
39
|
+
ref,
|
|
40
|
+
) {
|
|
41
|
+
const app = useContext<any>(MiniAppContext);
|
|
42
|
+
const screen = useContext<any>(ScreenContext);
|
|
43
|
+
const arrowSize = 6;
|
|
44
|
+
const [tooltipSize, setTooltipSize] = useState({ width: 0, height: 0 });
|
|
45
|
+
|
|
46
|
+
const componentName = 'Tooltip';
|
|
47
|
+
const componentId = useMemo(() => {
|
|
48
|
+
if (accessibilityLabel) {
|
|
49
|
+
return accessibilityLabel;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return `${app.appId}/${app.code}/${screen.screenName}/${componentName}`;
|
|
53
|
+
}, [accessibilityLabel, app, screen, componentName]);
|
|
54
|
+
|
|
55
|
+
const isControlled = typeof visible === 'boolean';
|
|
56
|
+
const [internalVisible, setInternalVisible] = useState(false);
|
|
57
|
+
const animatedValue = useRef(
|
|
58
|
+
new Animated.Value(visible ?? internalVisible ? 1 : 0),
|
|
59
|
+
).current;
|
|
60
|
+
const showTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
61
|
+
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
62
|
+
const [anchorSize, setAnchorSize] = useState({ width: 0, height: 0 });
|
|
63
|
+
|
|
64
|
+
const currentVisible = isControlled ? !!visible : internalVisible;
|
|
65
|
+
|
|
66
|
+
const clearTimers = () => {
|
|
67
|
+
if (showTimer.current) {
|
|
68
|
+
clearTimeout(showTimer.current);
|
|
69
|
+
}
|
|
70
|
+
if (hideTimer.current) {
|
|
71
|
+
clearTimeout(hideTimer.current);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const setVisibility = (nextVisible: boolean) => {
|
|
76
|
+
if (!isControlled) {
|
|
77
|
+
setInternalVisible(nextVisible);
|
|
78
|
+
}
|
|
79
|
+
onVisibleChange?.(nextVisible);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const animate = (nextVisible: boolean) => {
|
|
83
|
+
Animated.timing(animatedValue, {
|
|
84
|
+
toValue: nextVisible ? 1 : 0,
|
|
85
|
+
duration: animationDuration,
|
|
86
|
+
useNativeDriver: true,
|
|
87
|
+
easing: nextVisible ? Easing.out(Easing.quad) : Easing.in(Easing.quad),
|
|
88
|
+
}).start();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
animate(currentVisible);
|
|
93
|
+
}, [currentVisible, animationDuration]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
return () => {
|
|
97
|
+
clearTimers();
|
|
98
|
+
animatedValue.stopAnimation();
|
|
99
|
+
};
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
102
|
+
const handleShow = () => {
|
|
103
|
+
clearTimers();
|
|
104
|
+
showTimer.current = setTimeout(() => setVisibility(true), showDelay);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const handleHide = () => {
|
|
108
|
+
clearTimers();
|
|
109
|
+
hideTimer.current = setTimeout(() => setVisibility(false), hideDelay);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleToggle = () => {
|
|
113
|
+
if (currentVisible) {
|
|
114
|
+
handleHide();
|
|
115
|
+
} else {
|
|
116
|
+
handleShow();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
useImperativeHandle(
|
|
121
|
+
ref,
|
|
122
|
+
() => ({
|
|
123
|
+
show: handleShow,
|
|
124
|
+
hide: handleHide,
|
|
125
|
+
toggle: handleToggle,
|
|
126
|
+
}),
|
|
127
|
+
[currentVisible, showDelay, hideDelay],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const translate =
|
|
131
|
+
placement === 'top'
|
|
132
|
+
? {
|
|
133
|
+
translateY: animatedValue.interpolate({
|
|
134
|
+
inputRange: [0, 1],
|
|
135
|
+
outputRange: [4, 0],
|
|
136
|
+
}),
|
|
137
|
+
}
|
|
138
|
+
: placement === 'bottom'
|
|
139
|
+
? {
|
|
140
|
+
translateY: animatedValue.interpolate({
|
|
141
|
+
inputRange: [0, 1],
|
|
142
|
+
outputRange: [-4, 0],
|
|
143
|
+
}),
|
|
144
|
+
}
|
|
145
|
+
: placement === 'left'
|
|
146
|
+
? {
|
|
147
|
+
translateX: animatedValue.interpolate({
|
|
148
|
+
inputRange: [0, 1],
|
|
149
|
+
outputRange: [4, 0],
|
|
150
|
+
}),
|
|
151
|
+
}
|
|
152
|
+
: {
|
|
153
|
+
translateX: animatedValue.interpolate({
|
|
154
|
+
inputRange: [0, 1],
|
|
155
|
+
outputRange: [-4, 0],
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const placementStyle: {
|
|
160
|
+
[key: string]: any;
|
|
161
|
+
} =
|
|
162
|
+
placement === 'top'
|
|
163
|
+
? { bottom: (anchorSize.height || 0) + offset }
|
|
164
|
+
: placement === 'bottom'
|
|
165
|
+
? { top: (anchorSize.height || 0) + offset }
|
|
166
|
+
: placement === 'left'
|
|
167
|
+
? { right: (anchorSize.width || 0) + offset }
|
|
168
|
+
: { left: (anchorSize.width || 0) + offset };
|
|
169
|
+
|
|
170
|
+
const centerVerticalOffset =
|
|
171
|
+
(anchorSize.height || 0) / 2 - (tooltipSize.height || 0) / 2;
|
|
172
|
+
|
|
173
|
+
const alignStyle: ViewStyle =
|
|
174
|
+
placement === 'top' || placement === 'bottom'
|
|
175
|
+
? align === 'start'
|
|
176
|
+
? { left: 0 }
|
|
177
|
+
: align === 'end'
|
|
178
|
+
? { right: 0 }
|
|
179
|
+
: { alignSelf: 'center' }
|
|
180
|
+
: align === 'start'
|
|
181
|
+
? { top: 0 }
|
|
182
|
+
: align === 'end'
|
|
183
|
+
? { bottom: 0 }
|
|
184
|
+
: { top: centerVerticalOffset };
|
|
185
|
+
|
|
186
|
+
const resolvedBackground = Colors.black_17;
|
|
187
|
+
const resolvedTextColor = Colors.black_01;
|
|
188
|
+
|
|
189
|
+
const renderButtons = () => {
|
|
190
|
+
if (!buttons.length) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (buttons.length === 1) {
|
|
195
|
+
const btn = buttons[0];
|
|
196
|
+
const onPress = btn.onPress ?? (() => {});
|
|
197
|
+
return (
|
|
198
|
+
<View style={styles.buttonsRow}>
|
|
199
|
+
<View
|
|
200
|
+
style={[
|
|
201
|
+
styles.button,
|
|
202
|
+
btn.icon ? styles.iconButton : styles.textButton,
|
|
203
|
+
]}
|
|
204
|
+
>
|
|
205
|
+
{btn.icon ? (
|
|
206
|
+
<IconButton icon={btn.icon} onPress={onPress} />
|
|
207
|
+
) : (
|
|
208
|
+
<PrimaryButton title={btn.title || ''} onPress={onPress} />
|
|
209
|
+
)}
|
|
210
|
+
</View>
|
|
211
|
+
</View>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (buttons.length === 2) {
|
|
216
|
+
const [first, second] = buttons;
|
|
217
|
+
const firstPress = first.onPress ?? (() => {});
|
|
218
|
+
const secondPress = second.onPress ?? (() => {});
|
|
219
|
+
const bothIcon = !!first.icon && !!second.icon;
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<View style={styles.buttonsRow}>
|
|
223
|
+
{bothIcon ? (
|
|
224
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
225
|
+
<IconButton
|
|
226
|
+
icon={second.icon as string}
|
|
227
|
+
onPress={secondPress}
|
|
228
|
+
style={{ marginRight: Spacing.S }}
|
|
229
|
+
/>
|
|
230
|
+
<IconButton icon={first.icon as string} onPress={firstPress} />
|
|
231
|
+
</View>
|
|
232
|
+
) : (
|
|
233
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
234
|
+
<SecondaryButton
|
|
235
|
+
title={second.title || ''}
|
|
236
|
+
onPress={secondPress}
|
|
237
|
+
style={{ marginRight: Spacing.S }}
|
|
238
|
+
/>
|
|
239
|
+
<PrimaryButton title={first.title || ''} onPress={firstPress} />
|
|
240
|
+
</View>
|
|
241
|
+
)}
|
|
242
|
+
</View>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<View style={styles.buttonsRow}>
|
|
248
|
+
{buttons.map((btn, idx) => {
|
|
249
|
+
const isIcon = !!btn.icon;
|
|
250
|
+
const key = btn.title || btn.icon || `${idx}`;
|
|
251
|
+
const onPress = btn.onPress ?? (() => {});
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<View
|
|
255
|
+
key={key}
|
|
256
|
+
style={[
|
|
257
|
+
styles.button,
|
|
258
|
+
isIcon ? styles.iconButton : styles.textButton,
|
|
259
|
+
]}
|
|
260
|
+
>
|
|
261
|
+
{isIcon ? (
|
|
262
|
+
<IconButton icon={btn.icon as string} onPress={onPress} />
|
|
263
|
+
) : (
|
|
264
|
+
<PrimaryButton title={btn.title || ''} onPress={onPress} />
|
|
265
|
+
)}
|
|
266
|
+
</View>
|
|
267
|
+
);
|
|
268
|
+
})}
|
|
269
|
+
</View>
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const renderContent = () => {
|
|
274
|
+
return (
|
|
275
|
+
<View style={styles.content}>
|
|
276
|
+
<View style={{ flexDirection: 'row' }}>
|
|
277
|
+
<View style={{ flex: 1 }}>
|
|
278
|
+
{title ? (
|
|
279
|
+
<Text
|
|
280
|
+
typography={'header_s_semibold'}
|
|
281
|
+
color={resolvedTextColor}
|
|
282
|
+
style={styles.title}
|
|
283
|
+
>
|
|
284
|
+
{title}
|
|
285
|
+
</Text>
|
|
286
|
+
) : null}
|
|
287
|
+
{description ? (
|
|
288
|
+
<Text
|
|
289
|
+
typography={'description_default_regular'}
|
|
290
|
+
color={resolvedTextColor}
|
|
291
|
+
style={styles.description}
|
|
292
|
+
>
|
|
293
|
+
{description}
|
|
294
|
+
</Text>
|
|
295
|
+
) : null}
|
|
296
|
+
</View>
|
|
297
|
+
<Icon
|
|
298
|
+
source={'navigation_close'}
|
|
299
|
+
size={20}
|
|
300
|
+
color={Colors.black_01}
|
|
301
|
+
style={{ marginLeft: Spacing.S }}
|
|
302
|
+
/>
|
|
303
|
+
</View>
|
|
304
|
+
{renderButtons()}
|
|
305
|
+
</View>
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const getArrowStyle: () => object[] = () => {
|
|
310
|
+
const size = arrowSize;
|
|
311
|
+
const common = [
|
|
312
|
+
styles.arrow,
|
|
313
|
+
{
|
|
314
|
+
width: size * 2,
|
|
315
|
+
height: size * 2,
|
|
316
|
+
backgroundColor: resolvedBackground,
|
|
317
|
+
transform: [{ rotate: '45deg' }],
|
|
318
|
+
borderRadius: size / 2,
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
switch (placement) {
|
|
323
|
+
case 'top':
|
|
324
|
+
return [
|
|
325
|
+
...common,
|
|
326
|
+
{
|
|
327
|
+
bottom: -(size - 1),
|
|
328
|
+
...(align === 'start'
|
|
329
|
+
? { left: size + Spacing.M }
|
|
330
|
+
: align === 'end'
|
|
331
|
+
? { right: size + Spacing.M }
|
|
332
|
+
: { alignSelf: 'center' }),
|
|
333
|
+
},
|
|
334
|
+
];
|
|
335
|
+
case 'bottom':
|
|
336
|
+
return [
|
|
337
|
+
...common,
|
|
338
|
+
{
|
|
339
|
+
top: -(size - 1),
|
|
340
|
+
...(align === 'start'
|
|
341
|
+
? { left: size + Spacing.M }
|
|
342
|
+
: align === 'end'
|
|
343
|
+
? { right: size + Spacing.M }
|
|
344
|
+
: { alignSelf: 'center' }),
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
case 'left':
|
|
348
|
+
return [
|
|
349
|
+
...common,
|
|
350
|
+
{
|
|
351
|
+
right: -(size - 1),
|
|
352
|
+
...(align === 'start'
|
|
353
|
+
? { top: size + Spacing.M }
|
|
354
|
+
: align === 'end'
|
|
355
|
+
? { bottom: size + Spacing.M }
|
|
356
|
+
: { top: (tooltipSize.height || 0) / 2 - size }),
|
|
357
|
+
},
|
|
358
|
+
];
|
|
359
|
+
case 'right':
|
|
360
|
+
default:
|
|
361
|
+
return [
|
|
362
|
+
...common,
|
|
363
|
+
{
|
|
364
|
+
left: -(size - 1),
|
|
365
|
+
...(align === 'start'
|
|
366
|
+
? { top: size + Spacing.M }
|
|
367
|
+
: align === 'end'
|
|
368
|
+
? { bottom: size + Spacing.M }
|
|
369
|
+
: { top: (tooltipSize.height || 0) / 2 - size }),
|
|
370
|
+
},
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const tooltipNode = (
|
|
376
|
+
<Animated.View
|
|
377
|
+
pointerEvents="auto"
|
|
378
|
+
style={[
|
|
379
|
+
styles.tooltip,
|
|
380
|
+
placementStyle,
|
|
381
|
+
alignStyle,
|
|
382
|
+
{
|
|
383
|
+
opacity: animatedValue,
|
|
384
|
+
transform: [translate],
|
|
385
|
+
backgroundColor: resolvedBackground,
|
|
386
|
+
},
|
|
387
|
+
]}
|
|
388
|
+
onLayout={event => setTooltipSize(event.nativeEvent.layout)}
|
|
389
|
+
>
|
|
390
|
+
{renderContent()}
|
|
391
|
+
<View style={getArrowStyle()} />
|
|
392
|
+
</Animated.View>
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<View
|
|
397
|
+
pointerEvents={'box-none'}
|
|
398
|
+
style={styles.container}
|
|
399
|
+
accessibilityLabel={componentId}
|
|
400
|
+
>
|
|
401
|
+
<View onLayout={event => setAnchorSize(event.nativeEvent.layout)}>
|
|
402
|
+
{children}
|
|
403
|
+
</View>
|
|
404
|
+
{tooltipNode}
|
|
405
|
+
</View>
|
|
406
|
+
);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
export { Tooltip };
|
|
410
|
+
export type { TooltipProps, TooltipPlacement, TooltipRef };
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@momo-kits/animated-tooltip",
|
|
3
|
+
"version": "0.153.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"main": "index.tsx",
|
|
6
|
+
"dependencies": {},
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"@momo-kits/foundation": "latest",
|
|
9
|
+
"react": "*",
|
|
10
|
+
"react-native": "*"
|
|
11
|
+
},
|
|
12
|
+
"license": "MoMo",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"registry": "https://registry.npmjs.org/"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
package/publish.sh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
if [ "$1" == "stable" ]; then
|
|
4
|
+
npm version $(npm view @momo-kits/foundation@stable version)
|
|
5
|
+
npm version patch
|
|
6
|
+
npm publish --tag stable --access=public
|
|
7
|
+
elif [ "$1" == "latest" ]; then
|
|
8
|
+
npm publish --tag latest --access=public
|
|
9
|
+
elif [ "$1" == "beta" ]; then
|
|
10
|
+
npm publish --tag beta --access=public
|
|
11
|
+
else
|
|
12
|
+
npm publish --tag alpha --access=public
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
PACKAGE_NAME=$(npm pkg get name)
|
|
16
|
+
NEW_PACKAGE_VERSION=$(npm pkg get version)
|
|
17
|
+
|
|
18
|
+
|
package/styles.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors, Radius, Spacing } from '@momo-kits/foundation';
|
|
3
|
+
|
|
4
|
+
export default StyleSheet.create({
|
|
5
|
+
container: {
|
|
6
|
+
position: 'relative',
|
|
7
|
+
},
|
|
8
|
+
tooltip: {
|
|
9
|
+
position: 'absolute',
|
|
10
|
+
zIndex: 10,
|
|
11
|
+
padding: Spacing.M,
|
|
12
|
+
backgroundColor: Colors.black_17,
|
|
13
|
+
borderRadius: Radius.S,
|
|
14
|
+
width: 256,
|
|
15
|
+
},
|
|
16
|
+
text: {
|
|
17
|
+
color: Colors.black_01,
|
|
18
|
+
},
|
|
19
|
+
arrow: {
|
|
20
|
+
position: 'absolute',
|
|
21
|
+
width: 0,
|
|
22
|
+
height: 0,
|
|
23
|
+
},
|
|
24
|
+
content: {
|
|
25
|
+
flexDirection: 'column',
|
|
26
|
+
},
|
|
27
|
+
title: {
|
|
28
|
+
marginBottom: Spacing.XS / 2,
|
|
29
|
+
},
|
|
30
|
+
description: {
|
|
31
|
+
marginBottom: Spacing.XS / 2,
|
|
32
|
+
},
|
|
33
|
+
buttonsRow: {
|
|
34
|
+
flexDirection: 'row',
|
|
35
|
+
flexWrap: 'wrap',
|
|
36
|
+
justifyContent: 'flex-end',
|
|
37
|
+
marginTop: Spacing.M,
|
|
38
|
+
},
|
|
39
|
+
button: {
|
|
40
|
+
marginRight: Spacing.XS / 2,
|
|
41
|
+
marginBottom: Spacing.XS / 2,
|
|
42
|
+
},
|
|
43
|
+
textButton: {},
|
|
44
|
+
iconButton: {},
|
|
45
|
+
});
|
package/types.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {ReactNode} from 'react';
|
|
2
|
+
|
|
3
|
+
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
|
4
|
+
|
|
5
|
+
export type TooltipRef = {
|
|
6
|
+
show: () => void;
|
|
7
|
+
hide: () => void;
|
|
8
|
+
toggle: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type TooltipButton = {
|
|
12
|
+
title?: string;
|
|
13
|
+
icon?: string;
|
|
14
|
+
onPress?: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type TooltipProps = {
|
|
18
|
+
/**
|
|
19
|
+
* Element that the tooltip is anchored to.
|
|
20
|
+
*/
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* Title displayed at the top of the tooltip.
|
|
24
|
+
*/
|
|
25
|
+
title?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Description text shown under the title.
|
|
28
|
+
*/
|
|
29
|
+
description?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Action buttons rendered at the bottom of the tooltip.
|
|
32
|
+
*/
|
|
33
|
+
buttons?: TooltipButton[];
|
|
34
|
+
/**
|
|
35
|
+
* Tooltip position relative to the anchor element.
|
|
36
|
+
* @default 'top'
|
|
37
|
+
*/
|
|
38
|
+
placement?: TooltipPlacement;
|
|
39
|
+
/**
|
|
40
|
+
* Cross-axis alignment (start/center/end).
|
|
41
|
+
* @default 'center'
|
|
42
|
+
*/
|
|
43
|
+
align?: 'start' | 'center' | 'end';
|
|
44
|
+
/**
|
|
45
|
+
* Controlled visibility. Internal state is ignored when provided.
|
|
46
|
+
*/
|
|
47
|
+
visible?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Delay (ms) before showing the tooltip.
|
|
50
|
+
* @default 120
|
|
51
|
+
*/
|
|
52
|
+
showDelay?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Delay (ms) before hiding the tooltip.
|
|
55
|
+
* @default 120
|
|
56
|
+
*/
|
|
57
|
+
hideDelay?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Duration (ms) of the show/hide animation.
|
|
60
|
+
* @default 180
|
|
61
|
+
*/
|
|
62
|
+
animationDuration?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Gap between the tooltip and its anchor.
|
|
65
|
+
* @default 8
|
|
66
|
+
*/
|
|
67
|
+
offset?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Accessibility label for the component.
|
|
70
|
+
*/
|
|
71
|
+
accessibilityLabel?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Triggered when visibility changes.
|
|
74
|
+
*/
|
|
75
|
+
onVisibleChange?: (visible: boolean) => void;
|
|
76
|
+
};
|
|
77
|
+
|