react-native-lumen 1.0.0
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/LICENSE +20 -0
- package/README.md +231 -0
- package/lib/module/components/TourOverlay.js +134 -0
- package/lib/module/components/TourOverlay.js.map +1 -0
- package/lib/module/components/TourProvider.js +233 -0
- package/lib/module/components/TourProvider.js.map +1 -0
- package/lib/module/components/TourTooltip.js +233 -0
- package/lib/module/components/TourTooltip.js.map +1 -0
- package/lib/module/components/TourZone.js +246 -0
- package/lib/module/components/TourZone.js.map +1 -0
- package/lib/module/constants/animations.js +72 -0
- package/lib/module/constants/animations.js.map +1 -0
- package/lib/module/constants/defaults.js +14 -0
- package/lib/module/constants/defaults.js.map +1 -0
- package/lib/module/hooks/useTour.js +12 -0
- package/lib/module/hooks/useTour.js.map +1 -0
- package/lib/module/index.js +11 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types/index.js +4 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/components/TourOverlay.d.ts +2 -0
- package/lib/typescript/src/components/TourOverlay.d.ts.map +1 -0
- package/lib/typescript/src/components/TourProvider.d.ts +21 -0
- package/lib/typescript/src/components/TourProvider.d.ts.map +1 -0
- package/lib/typescript/src/components/TourTooltip.d.ts +2 -0
- package/lib/typescript/src/components/TourTooltip.d.ts.map +1 -0
- package/lib/typescript/src/components/TourZone.d.ts +16 -0
- package/lib/typescript/src/components/TourZone.d.ts.map +1 -0
- package/lib/typescript/src/constants/animations.d.ts +34 -0
- package/lib/typescript/src/constants/animations.d.ts.map +1 -0
- package/lib/typescript/src/constants/defaults.d.ts +10 -0
- package/lib/typescript/src/constants/defaults.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useTour.d.ts +2 -0
- package/lib/typescript/src/hooks/useTour.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +9 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +135 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -0
- package/package.json +171 -0
- package/src/components/TourOverlay.tsx +153 -0
- package/src/components/TourProvider.tsx +361 -0
- package/src/components/TourTooltip.tsx +252 -0
- package/src/components/TourZone.tsx +372 -0
- package/src/constants/animations.ts +71 -0
- package/src/constants/defaults.ts +15 -0
- package/src/hooks/useTour.ts +10 -0
- package/src/index.tsx +8 -0
- package/src/types/index.ts +142 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useEffect,
|
|
3
|
+
useCallback,
|
|
4
|
+
useRef,
|
|
5
|
+
type ComponentType,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import type { ViewStyle, StyleProp } from 'react-native';
|
|
8
|
+
import { useTour } from '../hooks/useTour';
|
|
9
|
+
import {
|
|
10
|
+
useAnimatedRef,
|
|
11
|
+
measure,
|
|
12
|
+
useFrameCallback,
|
|
13
|
+
withSpring,
|
|
14
|
+
default as Animated,
|
|
15
|
+
type AnimatedRef,
|
|
16
|
+
useSharedValue,
|
|
17
|
+
} from 'react-native-reanimated';
|
|
18
|
+
import { Dimensions } from 'react-native';
|
|
19
|
+
import type { InternalTourContextType } from '../types';
|
|
20
|
+
|
|
21
|
+
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
22
|
+
|
|
23
|
+
const AnimatedView = Animated.View as unknown as ComponentType<any>;
|
|
24
|
+
|
|
25
|
+
interface TourZoneProps {
|
|
26
|
+
stepKey: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
description: string;
|
|
29
|
+
order?: number;
|
|
30
|
+
shape?: 'rect' | 'circle';
|
|
31
|
+
borderRadius?: number;
|
|
32
|
+
children: React.ReactNode;
|
|
33
|
+
style?: StyleProp<ViewStyle>;
|
|
34
|
+
clickable?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const TourZone: React.FC<TourZoneProps> = ({
|
|
38
|
+
stepKey,
|
|
39
|
+
name,
|
|
40
|
+
description,
|
|
41
|
+
order,
|
|
42
|
+
shape = 'rect',
|
|
43
|
+
borderRadius = 10,
|
|
44
|
+
children,
|
|
45
|
+
style,
|
|
46
|
+
clickable,
|
|
47
|
+
}) => {
|
|
48
|
+
const {
|
|
49
|
+
registerStep,
|
|
50
|
+
unregisterStep,
|
|
51
|
+
updateStepLayout,
|
|
52
|
+
currentStep,
|
|
53
|
+
containerRef,
|
|
54
|
+
scrollViewRef,
|
|
55
|
+
targetX,
|
|
56
|
+
targetY,
|
|
57
|
+
targetWidth,
|
|
58
|
+
targetHeight,
|
|
59
|
+
targetRadius,
|
|
60
|
+
config,
|
|
61
|
+
} = useTour() as InternalTourContextType;
|
|
62
|
+
const viewRef = useAnimatedRef<any>();
|
|
63
|
+
|
|
64
|
+
const isActive = currentStep === stepKey;
|
|
65
|
+
|
|
66
|
+
// Track if we're currently scrolling to prevent position updates during scroll
|
|
67
|
+
const isScrolling = useSharedValue(false);
|
|
68
|
+
const hasScrolled = useRef(false);
|
|
69
|
+
|
|
70
|
+
// Signal when scroll completes (from JS thread)
|
|
71
|
+
const onScrollComplete = useCallback(() => {
|
|
72
|
+
isScrolling.value = false;
|
|
73
|
+
}, [isScrolling]);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* UNIFIED MEASUREMENT FUNCTION (JS THREAD)
|
|
77
|
+
* Always measures relative to SCREEN (Viewport), not Content.
|
|
78
|
+
* This fixes the bug where measureLayout returned content-relative Y.
|
|
79
|
+
*/
|
|
80
|
+
const measureJS = useCallback(() => {
|
|
81
|
+
if (isScrolling.value || !isActive) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const view = viewRef.current as any;
|
|
86
|
+
const container = containerRef.current as any;
|
|
87
|
+
|
|
88
|
+
if (view && container) {
|
|
89
|
+
// 1. Measure the View in Screen Coordinates (PageX/PageY)
|
|
90
|
+
view.measure(
|
|
91
|
+
(
|
|
92
|
+
_x: number,
|
|
93
|
+
_y: number,
|
|
94
|
+
width: number,
|
|
95
|
+
height: number,
|
|
96
|
+
pageX: number,
|
|
97
|
+
pageY: number
|
|
98
|
+
) => {
|
|
99
|
+
// 2. Measure the Container (TourOverlay) in Screen Coordinates
|
|
100
|
+
// This handles cases where the Tour Overlay isn't exactly at 0,0 (e.g. inside a SafeAreaView)
|
|
101
|
+
container.measure(
|
|
102
|
+
(
|
|
103
|
+
_cx: number,
|
|
104
|
+
_cy: number,
|
|
105
|
+
_cw: number,
|
|
106
|
+
_ch: number,
|
|
107
|
+
containerPageX: number,
|
|
108
|
+
containerPageY: number
|
|
109
|
+
) => {
|
|
110
|
+
if (width > 0 && height > 0 && !isNaN(pageX) && !isNaN(pageY)) {
|
|
111
|
+
// Calculate final position relative to the Tour Overlay
|
|
112
|
+
const finalX = pageX - containerPageX;
|
|
113
|
+
const finalY = pageY - containerPageY;
|
|
114
|
+
|
|
115
|
+
updateStepLayout(stepKey, {
|
|
116
|
+
x: finalX,
|
|
117
|
+
y: finalY,
|
|
118
|
+
width,
|
|
119
|
+
height,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}, [containerRef, stepKey, updateStepLayout, viewRef, isScrolling, isActive]);
|
|
128
|
+
|
|
129
|
+
// Initial measurement when step becomes active
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!isActive) return;
|
|
132
|
+
|
|
133
|
+
// Small delay to ensure layout is ready
|
|
134
|
+
const timeoutId = setTimeout(() => {
|
|
135
|
+
measureJS();
|
|
136
|
+
}, 50);
|
|
137
|
+
|
|
138
|
+
return () => clearTimeout(timeoutId);
|
|
139
|
+
}, [isActive, measureJS]);
|
|
140
|
+
|
|
141
|
+
// Reanimated Frame Callback (UI Thread Tracking)
|
|
142
|
+
// This keeps the highlight sticky during manual user scrolling
|
|
143
|
+
useFrameCallback(() => {
|
|
144
|
+
'worklet';
|
|
145
|
+
if (!isActive || isScrolling.value) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const measured = measure(viewRef);
|
|
150
|
+
const container = measure(containerRef as AnimatedRef<any>);
|
|
151
|
+
|
|
152
|
+
if (measured && container) {
|
|
153
|
+
const x = measured.pageX - container.pageX;
|
|
154
|
+
const y = measured.pageY - container.pageY;
|
|
155
|
+
const width = measured.width;
|
|
156
|
+
const height = measured.height;
|
|
157
|
+
|
|
158
|
+
if (
|
|
159
|
+
width > 0 &&
|
|
160
|
+
height > 0 &&
|
|
161
|
+
!isNaN(x) &&
|
|
162
|
+
!isNaN(y) &&
|
|
163
|
+
isFinite(x) &&
|
|
164
|
+
isFinite(y)
|
|
165
|
+
) {
|
|
166
|
+
const springConfig = config?.springConfig ?? {
|
|
167
|
+
damping: 100,
|
|
168
|
+
stiffness: 100,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
targetX.value = withSpring(x, springConfig);
|
|
172
|
+
targetY.value = withSpring(y, springConfig);
|
|
173
|
+
targetWidth.value = withSpring(width, springConfig);
|
|
174
|
+
targetHeight.value = withSpring(height, springConfig);
|
|
175
|
+
targetRadius.value = withSpring(borderRadius, springConfig);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {
|
|
179
|
+
// Silently ignore measurement errors on UI thread
|
|
180
|
+
}
|
|
181
|
+
}, isActive);
|
|
182
|
+
|
|
183
|
+
// Auto-scroll Effect
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (!isActive || !scrollViewRef?.current || !viewRef.current) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
hasScrolled.current = false;
|
|
190
|
+
const view = viewRef.current as any;
|
|
191
|
+
const scroll = scrollViewRef.current as any;
|
|
192
|
+
const container = containerRef.current as any;
|
|
193
|
+
|
|
194
|
+
let attemptCount = 0;
|
|
195
|
+
const maxAttempts = 3;
|
|
196
|
+
|
|
197
|
+
const attemptMeasurement = (delay: number) => {
|
|
198
|
+
const timeoutId = setTimeout(() => {
|
|
199
|
+
if (hasScrolled.current) return;
|
|
200
|
+
|
|
201
|
+
attemptCount++;
|
|
202
|
+
|
|
203
|
+
// 1. Check current visibility on screen
|
|
204
|
+
view.measure(
|
|
205
|
+
(
|
|
206
|
+
_mx: number,
|
|
207
|
+
_my: number,
|
|
208
|
+
mw: number,
|
|
209
|
+
mh: number,
|
|
210
|
+
px: number,
|
|
211
|
+
py: number
|
|
212
|
+
) => {
|
|
213
|
+
if (mw > 0 && mh > 0 && !isNaN(px) && !isNaN(py)) {
|
|
214
|
+
const viewportHeight = SCREEN_HEIGHT;
|
|
215
|
+
const topBuffer = 100;
|
|
216
|
+
const bottomBuffer = 150;
|
|
217
|
+
|
|
218
|
+
// Check if element is out of the "safe" visual zone
|
|
219
|
+
const needsScroll =
|
|
220
|
+
py < topBuffer || py + mh > viewportHeight - bottomBuffer;
|
|
221
|
+
|
|
222
|
+
if (needsScroll) {
|
|
223
|
+
hasScrolled.current = true;
|
|
224
|
+
isScrolling.value = true;
|
|
225
|
+
|
|
226
|
+
// 2. Measure ScrollView to get its Screen Position (Offset from top)
|
|
227
|
+
// This fixes the "upwards" bug by accounting for headers/safe-areas
|
|
228
|
+
scroll.measure(
|
|
229
|
+
(
|
|
230
|
+
_sx: number,
|
|
231
|
+
_sy: number,
|
|
232
|
+
_sw: number,
|
|
233
|
+
_sh: number,
|
|
234
|
+
scrollPx: number,
|
|
235
|
+
scrollPy: number
|
|
236
|
+
) => {
|
|
237
|
+
// 3. Measure Element relative to ScrollView Content
|
|
238
|
+
if (view.measureLayout) {
|
|
239
|
+
view.measureLayout(
|
|
240
|
+
scroll,
|
|
241
|
+
(contentX: number, contentY: number) => {
|
|
242
|
+
// Calculate target scroll position (center the element)
|
|
243
|
+
const centerY =
|
|
244
|
+
contentY - viewportHeight / 2 + mh / 2 + 50;
|
|
245
|
+
const scrollY = Math.max(0, centerY);
|
|
246
|
+
|
|
247
|
+
// 4. Measure Container to map coordinates to Overlay space
|
|
248
|
+
container.measure(
|
|
249
|
+
(
|
|
250
|
+
_cx: number,
|
|
251
|
+
_cy: number,
|
|
252
|
+
_cw: number,
|
|
253
|
+
_ch: number,
|
|
254
|
+
containerPx: number,
|
|
255
|
+
containerPy: number
|
|
256
|
+
) => {
|
|
257
|
+
// THE FIX: Add scrollPy (ScrollView's screen Y)
|
|
258
|
+
// Visual Y = ScrollViewScreenY + (ElementContentY - ScrollAmount)
|
|
259
|
+
const targetScreenY =
|
|
260
|
+
scrollPy + contentY - scrollY - containerPy;
|
|
261
|
+
|
|
262
|
+
// X is simpler: ScrollViewScreenX + ElementContentX - ContainerScreenX
|
|
263
|
+
const targetScreenX =
|
|
264
|
+
scrollPx + contentX - containerPx;
|
|
265
|
+
|
|
266
|
+
updateStepLayout(stepKey, {
|
|
267
|
+
x: targetScreenX,
|
|
268
|
+
y: targetScreenY,
|
|
269
|
+
width: mw,
|
|
270
|
+
height: mh,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
scroll.scrollTo({ y: scrollY, animated: true });
|
|
275
|
+
// Wait for scroll animation
|
|
276
|
+
setTimeout(() => onScrollComplete(), 800);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
console.error(e);
|
|
279
|
+
onScrollComplete();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
} else {
|
|
289
|
+
// Element is already visible - just sync position
|
|
290
|
+
container.measure(
|
|
291
|
+
(
|
|
292
|
+
_cx: number,
|
|
293
|
+
_cy: number,
|
|
294
|
+
_cw: number,
|
|
295
|
+
_ch: number,
|
|
296
|
+
cPx: number,
|
|
297
|
+
cPy: number
|
|
298
|
+
) => {
|
|
299
|
+
const finalX = px - cPx;
|
|
300
|
+
const finalY = py - cPy;
|
|
301
|
+
|
|
302
|
+
updateStepLayout(stepKey, {
|
|
303
|
+
x: finalX,
|
|
304
|
+
y: finalY,
|
|
305
|
+
width: mw,
|
|
306
|
+
height: mh,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
} else if (attemptCount < maxAttempts) {
|
|
312
|
+
attemptMeasurement(150 * attemptCount);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
}, delay);
|
|
317
|
+
return timeoutId;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const timeoutId = attemptMeasurement(150);
|
|
321
|
+
return () => clearTimeout(timeoutId);
|
|
322
|
+
}, [
|
|
323
|
+
isActive,
|
|
324
|
+
scrollViewRef,
|
|
325
|
+
viewRef,
|
|
326
|
+
stepKey,
|
|
327
|
+
isScrolling,
|
|
328
|
+
onScrollComplete,
|
|
329
|
+
containerRef,
|
|
330
|
+
updateStepLayout,
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
// Standard onLayout handler (uses the unified measureJS)
|
|
334
|
+
const onLayout = () => {
|
|
335
|
+
measureJS();
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Register step on mount
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
registerStep({
|
|
341
|
+
key: stepKey,
|
|
342
|
+
name,
|
|
343
|
+
description,
|
|
344
|
+
order,
|
|
345
|
+
clickable,
|
|
346
|
+
meta: { shape, borderRadius },
|
|
347
|
+
});
|
|
348
|
+
return () => unregisterStep(stepKey);
|
|
349
|
+
}, [
|
|
350
|
+
stepKey,
|
|
351
|
+
name,
|
|
352
|
+
description,
|
|
353
|
+
order,
|
|
354
|
+
shape,
|
|
355
|
+
borderRadius,
|
|
356
|
+
registerStep,
|
|
357
|
+
registerStep,
|
|
358
|
+
unregisterStep,
|
|
359
|
+
clickable,
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<AnimatedView
|
|
364
|
+
ref={viewRef}
|
|
365
|
+
onLayout={onLayout}
|
|
366
|
+
style={style}
|
|
367
|
+
collapsable={false}
|
|
368
|
+
>
|
|
369
|
+
{children}
|
|
370
|
+
</AnimatedView>
|
|
371
|
+
);
|
|
372
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { WithSpringConfig } from 'react-native-reanimated';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default spring configuration matching Reanimated 3 defaults.
|
|
5
|
+
*/
|
|
6
|
+
export const Reanimated3DefaultSpringConfig: WithSpringConfig = {
|
|
7
|
+
damping: 10,
|
|
8
|
+
mass: 1,
|
|
9
|
+
stiffness: 100,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Spring configuration with duration.
|
|
14
|
+
*/
|
|
15
|
+
export const Reanimated3DefaultSpringConfigWithDuration: WithSpringConfig = {
|
|
16
|
+
duration: 1333,
|
|
17
|
+
dampingRatio: 0.5,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A bouncy and energetic spring configuration.
|
|
22
|
+
*/
|
|
23
|
+
export const WigglySpringConfig: WithSpringConfig = {
|
|
24
|
+
damping: 90,
|
|
25
|
+
mass: 4,
|
|
26
|
+
stiffness: 900,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A bouncy spring configuration with fixed duration.
|
|
31
|
+
*/
|
|
32
|
+
export const WigglySpringConfigWithDuration: WithSpringConfig = {
|
|
33
|
+
duration: 550,
|
|
34
|
+
dampingRatio: 0.75,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A gentle and smooth spring configuration.
|
|
39
|
+
*/
|
|
40
|
+
export const GentleSpringConfig: WithSpringConfig = {
|
|
41
|
+
damping: 120,
|
|
42
|
+
mass: 4,
|
|
43
|
+
stiffness: 900,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A gentle spring configuration with fixed duration.
|
|
48
|
+
*/
|
|
49
|
+
export const GentleSpringConfigWithDuration: WithSpringConfig = {
|
|
50
|
+
duration: 550,
|
|
51
|
+
dampingRatio: 1,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A snappy and responsive spring configuration.
|
|
56
|
+
*/
|
|
57
|
+
export const SnappySpringConfig: WithSpringConfig = {
|
|
58
|
+
damping: 110,
|
|
59
|
+
mass: 4,
|
|
60
|
+
stiffness: 900,
|
|
61
|
+
overshootClamping: true,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A snappy spring configuration with fixed duration.
|
|
66
|
+
*/
|
|
67
|
+
export const SnappySpringConfigWithDuration: WithSpringConfig = {
|
|
68
|
+
duration: 550,
|
|
69
|
+
dampingRatio: 0.92,
|
|
70
|
+
overshootClamping: true,
|
|
71
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WithSpringConfig } from 'react-native-reanimated';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_SPRING_CONFIG: WithSpringConfig = {
|
|
4
|
+
damping: 20,
|
|
5
|
+
stiffness: 90,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_BACKDROP_OPACITY = 0.5;
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_LABELS = {
|
|
11
|
+
next: 'Next',
|
|
12
|
+
previous: 'Previous',
|
|
13
|
+
finish: 'Finish',
|
|
14
|
+
skip: 'Skip',
|
|
15
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { TourContext } from '../components/TourProvider';
|
|
3
|
+
|
|
4
|
+
export const useTour = () => {
|
|
5
|
+
const context = useContext(TourContext);
|
|
6
|
+
if (!context) {
|
|
7
|
+
throw new Error('useTour must be used within a TourProvider');
|
|
8
|
+
}
|
|
9
|
+
return context;
|
|
10
|
+
};
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './components/TourProvider';
|
|
3
|
+
export * from './components/TourZone';
|
|
4
|
+
export * from './hooks/useTour';
|
|
5
|
+
export { TourOverlay } from './components/TourOverlay';
|
|
6
|
+
export { TourTooltip } from './components/TourTooltip';
|
|
7
|
+
export * from './constants/defaults';
|
|
8
|
+
export * from './constants/animations';
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { WithSpringConfig, SharedValue } from 'react-native-reanimated';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export interface TourStep {
|
|
5
|
+
/**
|
|
6
|
+
* Unique key for this step.
|
|
7
|
+
*/
|
|
8
|
+
key: string;
|
|
9
|
+
/**
|
|
10
|
+
* Optional display name/label for this step.
|
|
11
|
+
*/
|
|
12
|
+
name?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Description text to show in the tooltip.
|
|
15
|
+
*/
|
|
16
|
+
description: string;
|
|
17
|
+
/**
|
|
18
|
+
* Optional order index. If not provided, registration order is used (or explicit ordering in context).
|
|
19
|
+
*/
|
|
20
|
+
order?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Optional data for custom tooltip rendering
|
|
23
|
+
*/
|
|
24
|
+
meta?: any;
|
|
25
|
+
/**
|
|
26
|
+
* If true, allows user interaction with the target element.
|
|
27
|
+
* If false, interactions are blocked (default behavior depends on global config).
|
|
28
|
+
*/
|
|
29
|
+
clickable?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MeasureResult {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type StepMap = Record<string, TourStep>;
|
|
40
|
+
|
|
41
|
+
export interface TourLabels {
|
|
42
|
+
next?: string;
|
|
43
|
+
previous?: string;
|
|
44
|
+
finish?: string;
|
|
45
|
+
skip?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface CardProps {
|
|
49
|
+
step: TourStep;
|
|
50
|
+
currentStepIndex: number;
|
|
51
|
+
totalSteps: number;
|
|
52
|
+
next: () => void;
|
|
53
|
+
prev: () => void;
|
|
54
|
+
stop: () => void;
|
|
55
|
+
isFirst: boolean;
|
|
56
|
+
isLast: boolean;
|
|
57
|
+
labels?: TourLabels;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TourConfig {
|
|
61
|
+
/**
|
|
62
|
+
* Animation configuration for the spotlight movement.
|
|
63
|
+
*/
|
|
64
|
+
springConfig?: WithSpringConfig;
|
|
65
|
+
/**
|
|
66
|
+
* If true, prevents interaction with the underlying app while tour is active.
|
|
67
|
+
* Default: false (interactions allowed outside the tooltip, but overlay might block them depending on implementation).
|
|
68
|
+
*/
|
|
69
|
+
preventInteraction?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Custom labels for buttons.
|
|
72
|
+
*/
|
|
73
|
+
labels?: TourLabels;
|
|
74
|
+
/**
|
|
75
|
+
* Custom renderer for the card/tooltip.
|
|
76
|
+
*/
|
|
77
|
+
renderCard?: (props: CardProps) => React.ReactNode;
|
|
78
|
+
/**
|
|
79
|
+
* Backdrop opacity. Default 0.5
|
|
80
|
+
*/
|
|
81
|
+
backdropOpacity?: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface TourContextType {
|
|
85
|
+
/**
|
|
86
|
+
* Starts the tour at the first step or a specific step (by key).
|
|
87
|
+
*/
|
|
88
|
+
start: (stepKey?: string) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Stops the tour and hides the overlay.
|
|
91
|
+
*/
|
|
92
|
+
stop: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* Advances to the next step.
|
|
95
|
+
*/
|
|
96
|
+
next: () => void;
|
|
97
|
+
/**
|
|
98
|
+
* Goes back to the previous step.
|
|
99
|
+
*/
|
|
100
|
+
prev: () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Registers a zone/step.
|
|
103
|
+
*/
|
|
104
|
+
registerStep: (step: TourStep) => void;
|
|
105
|
+
/**
|
|
106
|
+
* Unregisters a zone/step.
|
|
107
|
+
*/
|
|
108
|
+
unregisterStep: (key: string) => void;
|
|
109
|
+
/**
|
|
110
|
+
* Updates the layout of a specific step.
|
|
111
|
+
* This is called by TourZone on layout/mount.
|
|
112
|
+
*/
|
|
113
|
+
updateStepLayout: (key: string, measure: MeasureResult) => void;
|
|
114
|
+
/**
|
|
115
|
+
* The current active step key, or null if tour is inactive.
|
|
116
|
+
*/
|
|
117
|
+
currentStep: string | null;
|
|
118
|
+
/**
|
|
119
|
+
* Map of registered steps.
|
|
120
|
+
*/
|
|
121
|
+
steps: StepMap;
|
|
122
|
+
/**
|
|
123
|
+
* Global tour configuration
|
|
124
|
+
*/
|
|
125
|
+
config?: TourConfig;
|
|
126
|
+
/**
|
|
127
|
+
* Registers the main ScrollView ref for auto-scrolling
|
|
128
|
+
*/
|
|
129
|
+
setScrollViewRef: (ref: any) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface InternalTourContextType extends TourContextType {
|
|
133
|
+
targetX: SharedValue<number>;
|
|
134
|
+
targetY: SharedValue<number>;
|
|
135
|
+
targetWidth: SharedValue<number>;
|
|
136
|
+
targetHeight: SharedValue<number>;
|
|
137
|
+
targetRadius: SharedValue<number>;
|
|
138
|
+
opacity: SharedValue<number>;
|
|
139
|
+
containerRef: React.RefObject<any>;
|
|
140
|
+
scrollViewRef: React.RefObject<any>;
|
|
141
|
+
setScrollViewRef: (ref: any) => void;
|
|
142
|
+
}
|