react-native-lumen 1.1.0 → 1.1.1

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.
@@ -1,329 +0,0 @@
1
- import { useMemo, memo, useState, type ComponentType } from 'react';
2
- import {
3
- StyleSheet,
4
- Text,
5
- View,
6
- Dimensions,
7
- TouchableOpacity,
8
- } from 'react-native';
9
- import Animated, {
10
- useAnimatedStyle,
11
- useSharedValue,
12
- interpolate,
13
- Extrapolation,
14
- } from 'react-native-reanimated';
15
- import { useTour } from '../hooks/useTour';
16
- import type {
17
- CardProps,
18
- InternalTourContextType,
19
- TooltipStyles,
20
- } from '../types';
21
- import { DEFAULT_LABELS } from '../constants/defaults';
22
-
23
- const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
24
-
25
- const getDynamicStyles = (tooltipStyles?: TooltipStyles) => {
26
- const defaultStyles = {
27
- backgroundColor: tooltipStyles?.backgroundColor || 'white',
28
- borderRadius: tooltipStyles?.borderRadius || 12,
29
- titleColor: tooltipStyles?.titleColor || '#000',
30
- descriptionColor: tooltipStyles?.descriptionColor || '#444',
31
- primaryButtonColor: tooltipStyles?.primaryButtonColor || '#007AFF',
32
- primaryButtonTextColor: tooltipStyles?.primaryButtonTextColor || '#fff',
33
- primaryButtonBorderRadius: tooltipStyles?.primaryButtonBorderRadius || 25,
34
- skipButtonTextColor: tooltipStyles?.skipButtonTextColor || '#666',
35
- };
36
-
37
- return StyleSheet.create({
38
- cardStyle: {
39
- backgroundColor: defaultStyles.backgroundColor,
40
- borderRadius: defaultStyles.borderRadius,
41
- padding: 20,
42
- minHeight: 120,
43
- ...(tooltipStyles?.containerStyle || {}),
44
- },
45
- title: {
46
- fontSize: 18,
47
- fontWeight: 'bold',
48
- color: defaultStyles.titleColor,
49
- flex: 1,
50
- ...(tooltipStyles?.titleStyle || {}),
51
- },
52
- description: {
53
- fontSize: 15,
54
- color: defaultStyles.descriptionColor,
55
- marginBottom: 20,
56
- lineHeight: 22,
57
- ...(tooltipStyles?.descriptionStyle || {}),
58
- },
59
- buttonPrimary: {
60
- backgroundColor: defaultStyles.primaryButtonColor,
61
- paddingVertical: 10,
62
- paddingHorizontal: 20,
63
- borderRadius: defaultStyles.primaryButtonBorderRadius,
64
- ...(tooltipStyles?.primaryButtonStyle || {}),
65
- },
66
- primaryButtonText: {
67
- color: defaultStyles.primaryButtonTextColor,
68
- fontWeight: 'bold',
69
- fontSize: 14,
70
- ...(tooltipStyles?.primaryButtonTextStyle || {}),
71
- },
72
- skipText: {
73
- color: defaultStyles.skipButtonTextColor,
74
- fontWeight: '600',
75
- ...(tooltipStyles?.skipButtonTextStyle || {}),
76
- },
77
- buttonText: {
78
- padding: 8,
79
- ...(tooltipStyles?.skipButtonStyle || {}),
80
- },
81
- });
82
- };
83
-
84
- export const TourTooltip = memo(() => {
85
- const {
86
- targetX,
87
- targetY,
88
- targetWidth,
89
- targetHeight,
90
- currentStep,
91
- steps,
92
- next,
93
- prev,
94
- stop,
95
- opacity,
96
- config,
97
- orderedStepKeys,
98
- } = useTour() as InternalTourContextType;
99
-
100
- const currentStepData = currentStep ? steps[currentStep] : null;
101
-
102
- const tooltipHeight = useSharedValue(150);
103
- const [tooltipWidth] = useState(280);
104
-
105
- const dynamicStyles = useMemo(
106
- () => getDynamicStyles(config?.tooltipStyles),
107
- [config?.tooltipStyles]
108
- );
109
-
110
- // Use orderedStepKeys from context (consistent with TourProvider's ordering)
111
- const orderedSteps = orderedStepKeys;
112
-
113
- const currentIndex = currentStep ? orderedSteps.indexOf(currentStep) : -1;
114
- const totalSteps = orderedSteps.length;
115
- const isFirst = currentIndex === 0;
116
- const isLast = currentIndex === totalSteps - 1;
117
-
118
- const tooltipStyle = useAnimatedStyle(() => {
119
- 'worklet';
120
-
121
- const safeTargetX = targetX.value || 0;
122
- const safeTargetY = targetY.value || 0;
123
- const safeTargetWidth = Math.max(targetWidth.value || 0, 1);
124
- const safeTargetHeight = Math.max(targetHeight.value || 0, 1);
125
- const safeTooltipHeight = tooltipHeight.value || 150;
126
-
127
- // FIX: Aggressive Interpolation
128
- // Map the input value [0 -> 0.6] to output [0 -> 1].
129
- // This ensures that even if 'opacity.value' stops at 0.7 (backdrop level),
130
- // the tooltip is already fully opaque (1.0).
131
- const activeOpacity = interpolate(
132
- opacity.value,
133
- [0, 0.6],
134
- [0, 1],
135
- Extrapolation.CLAMP
136
- );
137
-
138
- const spaceAbove = safeTargetY;
139
- const spaceBelow = SCREEN_HEIGHT - (safeTargetY + safeTargetHeight);
140
-
141
- const shouldPlaceAbove =
142
- (spaceAbove > spaceBelow && spaceAbove > safeTooltipHeight + 30) ||
143
- (safeTargetY > SCREEN_HEIGHT / 2 && spaceAbove > safeTooltipHeight + 20);
144
-
145
- const horizontalCenter = safeTargetX + safeTargetWidth / 2;
146
- const left = horizontalCenter - tooltipWidth / 2;
147
-
148
- const clampedLeft = Math.max(
149
- 12,
150
- Math.min(SCREEN_WIDTH - tooltipWidth - 12, left)
151
- );
152
-
153
- const style: any = {
154
- position: 'absolute',
155
- width: tooltipWidth,
156
- left: clampedLeft,
157
- opacity: activeOpacity,
158
- backgroundColor: config?.tooltipStyles?.backgroundColor || 'white',
159
- transform: [{ translateY: interpolate(activeOpacity, [0, 1], [10, 0]) }],
160
- };
161
-
162
- if (shouldPlaceAbove) {
163
- style.top = Math.max(10, safeTargetY - safeTooltipHeight - 20);
164
- style.bottom = undefined;
165
- } else {
166
- style.top = safeTargetY + safeTargetHeight + 20;
167
- style.bottom = undefined;
168
- }
169
-
170
- return style;
171
- });
172
-
173
- if (!currentStepData) return null;
174
-
175
- const AnimatedView = Animated.View as unknown as ComponentType<any>;
176
-
177
- const handleTooltipLayout = (event: any) => {
178
- const { height } = event.nativeEvent.layout;
179
- if (height > 0) {
180
- tooltipHeight.value = height;
181
- }
182
- };
183
-
184
- // Build card props for custom renders
185
- const cardProps: CardProps = {
186
- step: currentStepData,
187
- currentStepIndex: currentIndex,
188
- totalSteps,
189
- next,
190
- prev,
191
- stop,
192
- isFirst,
193
- isLast,
194
- labels: config?.labels,
195
- required: currentStepData.required,
196
- completed: currentStepData.completed,
197
- };
198
-
199
- // Priority: Per-step custom card > Global custom card > Default card
200
- // 1. Per-step custom render (highest priority)
201
- if (currentStepData.renderCustomCard) {
202
- return (
203
- <AnimatedView
204
- style={[
205
- styles.container,
206
- tooltipStyle,
207
- // Reset styles for custom render so the user has full control
208
- styles.resetStyle,
209
- ]}
210
- onLayout={handleTooltipLayout}
211
- >
212
- {currentStepData.renderCustomCard(cardProps)}
213
- </AnimatedView>
214
- );
215
- }
216
-
217
- // 2. Global custom render
218
- if (config?.renderCard) {
219
- return (
220
- <AnimatedView
221
- style={[
222
- styles.container,
223
- tooltipStyle,
224
- // Reset styles for custom render so the user has full control
225
- styles.resetStyle,
226
- ]}
227
- onLayout={handleTooltipLayout}
228
- >
229
- {config.renderCard(cardProps)}
230
- </AnimatedView>
231
- );
232
- }
233
-
234
- // 3. Default Render
235
- const labels = { ...DEFAULT_LABELS, ...config?.labels };
236
- const labelNext = isLast ? labels.finish : labels.next;
237
- const labelSkip = labels.skip;
238
-
239
- const isRequired = currentStepData.required === true;
240
- const isNextDisabled = currentStepData.completed === false;
241
-
242
- return (
243
- <AnimatedView
244
- style={[styles.container, dynamicStyles.cardStyle, tooltipStyle]}
245
- onLayout={handleTooltipLayout}
246
- >
247
- <View style={styles.header}>
248
- <Text style={dynamicStyles.title}>
249
- {currentStepData.name || 'Step'}
250
- </Text>
251
- <Text style={styles.stepIndicator}>
252
- {currentIndex + 1} / {totalSteps}
253
- </Text>
254
- </View>
255
- <Text style={dynamicStyles.description}>
256
- {currentStepData.description}
257
- </Text>
258
-
259
- <View style={styles.footer}>
260
- {!isLast && !isRequired && (
261
- <TouchableOpacity onPress={stop} style={dynamicStyles.buttonText}>
262
- <Text style={dynamicStyles.skipText}>{labelSkip}</Text>
263
- </TouchableOpacity>
264
- )}
265
- {(isLast || isRequired) && <View style={styles.spacer} />}
266
-
267
- <TouchableOpacity
268
- onPress={isNextDisabled ? undefined : next}
269
- disabled={isNextDisabled}
270
- style={[
271
- dynamicStyles.buttonPrimary,
272
- isNextDisabled && styles.disabledButton,
273
- ]}
274
- >
275
- <Text
276
- style={[
277
- dynamicStyles.primaryButtonText,
278
- isNextDisabled && styles.disabledButtonText,
279
- ]}
280
- >
281
- {labelNext}
282
- </Text>
283
- </TouchableOpacity>
284
- </View>
285
- </AnimatedView>
286
- );
287
- });
288
-
289
- const styles = StyleSheet.create({
290
- container: {
291
- // Shadow Props
292
- shadowColor: '#000',
293
- shadowOffset: { width: 0, height: 4 },
294
- shadowOpacity: 0.3,
295
- shadowRadius: 5,
296
- elevation: 8,
297
- zIndex: 999,
298
- },
299
- header: {
300
- flexDirection: 'row',
301
- justifyContent: 'space-between',
302
- marginBottom: 8,
303
- },
304
- stepIndicator: {
305
- fontSize: 12,
306
- color: '#999',
307
- },
308
- footer: {
309
- flexDirection: 'row',
310
- justifyContent: 'space-between',
311
- alignItems: 'center',
312
- },
313
- resetStyle: {
314
- backgroundColor: 'transparent',
315
- shadowOpacity: 0,
316
- elevation: 0,
317
- padding: 0,
318
- borderRadius: 0,
319
- },
320
- spacer: {
321
- width: 10,
322
- },
323
- disabledButton: {
324
- opacity: 0.4,
325
- },
326
- disabledButtonText: {
327
- opacity: 0.7,
328
- },
329
- });