noboarding 1.0.3-beta โ 1.0.6-beta
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/ANIMATIONS.md +446 -0
- package/README.md +356 -323
- package/lib/OnboardingFlow.js +21 -4
- package/lib/animationUtils.d.ts +19 -0
- package/lib/animationUtils.js +252 -0
- package/lib/components/ElementRenderer.d.ts +5 -0
- package/lib/components/ElementRenderer.js +196 -36
- package/lib/types.d.ts +46 -0
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +19 -0
- package/src/animationUtils.ts +292 -0
- package/src/components/ElementRenderer.tsx +233 -18
- package/src/types.ts +51 -0
package/lib/OnboardingFlow.js
CHANGED
|
@@ -96,6 +96,7 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
96
96
|
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
97
97
|
const [error, setError] = (0, react_1.useState)(null);
|
|
98
98
|
const [screens, setScreens] = (0, react_1.useState)([]);
|
|
99
|
+
const [assets, setAssets] = (0, react_1.useState)([]);
|
|
99
100
|
const [currentIndex, setCurrentIndex] = (0, react_1.useState)(0);
|
|
100
101
|
const [collectedData, setCollectedData] = (0, react_1.useState)({});
|
|
101
102
|
const [variables, setVariables] = (0, react_1.useState)(initialVariables || {});
|
|
@@ -136,7 +137,7 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
136
137
|
}
|
|
137
138
|
}, [currentIndex, screens]);
|
|
138
139
|
const initializeFlow = async () => {
|
|
139
|
-
var _a, _b, _c;
|
|
140
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
140
141
|
try {
|
|
141
142
|
setLoading(true);
|
|
142
143
|
setError(null);
|
|
@@ -154,6 +155,10 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
154
155
|
const configResponse = await api.getConfig();
|
|
155
156
|
// Store flow_id for analytics
|
|
156
157
|
flowIdRef.current = configResponse.config_id;
|
|
158
|
+
// Store assets from config
|
|
159
|
+
if (configResponse.config.assets) {
|
|
160
|
+
setAssets(configResponse.config.assets);
|
|
161
|
+
}
|
|
157
162
|
// Handle A/B test experiment assignment
|
|
158
163
|
let screensToUse = configResponse.config.screens;
|
|
159
164
|
if (configResponse.experiments && configResponse.experiments.length > 0) {
|
|
@@ -161,11 +166,23 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
161
166
|
const experiment = configResponse.experiments[0];
|
|
162
167
|
try {
|
|
163
168
|
const assignment = await api.assignVariant(experiment.id, userIdRef.current);
|
|
169
|
+
console.log('๐งช A/B Test Assignment:', {
|
|
170
|
+
experiment_id: experiment.id,
|
|
171
|
+
experiment_name: experiment.name,
|
|
172
|
+
variant_id: assignment.variant_id,
|
|
173
|
+
has_variant_screens: ((_b = (_a = assignment.variant_config) === null || _a === void 0 ? void 0 : _a.screens) === null || _b === void 0 ? void 0 : _b.length) > 0,
|
|
174
|
+
variant_screen_count: ((_d = (_c = assignment.variant_config) === null || _c === void 0 ? void 0 : _c.screens) === null || _d === void 0 ? void 0 : _d.length) || 0,
|
|
175
|
+
cached: assignment.cached,
|
|
176
|
+
});
|
|
164
177
|
// Set experiment context so all events get tagged
|
|
165
178
|
analytics.setExperimentContext(experiment.id, assignment.variant_id);
|
|
166
179
|
// Use variant screens if available
|
|
167
|
-
if (((
|
|
180
|
+
if (((_f = (_e = assignment.variant_config) === null || _e === void 0 ? void 0 : _e.screens) === null || _f === void 0 ? void 0 : _f.length) > 0) {
|
|
168
181
|
screensToUse = assignment.variant_config.screens;
|
|
182
|
+
console.log('๐ฑ Using variant screens:', assignment.variant_config.screens.length, 'screens');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log('๐ฑ Using base flow screens (variant has no screens defined)');
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
188
|
catch (err) {
|
|
@@ -181,7 +198,7 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
181
198
|
// Track onboarding start with first screen
|
|
182
199
|
analytics.track('onboarding_started', {
|
|
183
200
|
flow_id: flowIdRef.current,
|
|
184
|
-
screen_id: (
|
|
201
|
+
screen_id: (_g = normalizedScreens[0]) === null || _g === void 0 ? void 0 : _g.id,
|
|
185
202
|
});
|
|
186
203
|
setLoading(false);
|
|
187
204
|
}
|
|
@@ -302,7 +319,7 @@ const OnboardingFlow = ({ apiKey, testKey, productionKey, onComplete, onSkip, ba
|
|
|
302
319
|
}
|
|
303
320
|
};
|
|
304
321
|
return (<react_native_1.View style={styles.container}>
|
|
305
|
-
<ElementRenderer_1.ElementRenderer elements={currentScreen.elements} analytics={analyticsRef.current} screenId={currentScreen.id} onNavigate={handleElementNavigate} onDismiss={onSkip ? handleSkipAll : handleNext} variables={allVariables} onSetVariable={handleSetVariable}/>
|
|
322
|
+
<ElementRenderer_1.ElementRenderer elements={currentScreen.elements} analytics={analyticsRef.current} screenId={currentScreen.id} onNavigate={handleElementNavigate} onDismiss={onSkip ? handleSkipAll : handleNext} variables={allVariables} onSetVariable={handleSetVariable} assets={assets}/>
|
|
306
323
|
</react_native_1.View>);
|
|
307
324
|
}
|
|
308
325
|
// Handle custom_screen type โ developer-registered React Native components
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Animated } from 'react-native';
|
|
2
|
+
import type { EntranceAnimation, InteractiveAnimation, HapticType } from './types';
|
|
3
|
+
export declare const triggerHaptic: (type?: HapticType) => void;
|
|
4
|
+
export declare const getEasing: (easingType?: string) => import("react-native").EasingFunction;
|
|
5
|
+
export interface EntranceAnimationValues {
|
|
6
|
+
opacity: Animated.Value;
|
|
7
|
+
translateY: Animated.Value;
|
|
8
|
+
translateX: Animated.Value;
|
|
9
|
+
scale: Animated.Value;
|
|
10
|
+
}
|
|
11
|
+
export declare const createEntranceAnimationValues: () => EntranceAnimationValues;
|
|
12
|
+
export declare const startEntranceAnimation: (config: EntranceAnimation, values: EntranceAnimationValues, delay?: number) => void;
|
|
13
|
+
export declare const startInteractiveAnimation: (config: InteractiveAnimation, animatedValue: Animated.Value) => void;
|
|
14
|
+
export interface TypewriterState {
|
|
15
|
+
displayedText: string;
|
|
16
|
+
currentIndex: number;
|
|
17
|
+
isComplete: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare const shouldTriggerHaptic: (index: number, frequency: string) => boolean;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shouldTriggerHaptic = exports.startInteractiveAnimation = exports.startEntranceAnimation = exports.createEntranceAnimationValues = exports.getEasing = exports.triggerHaptic = void 0;
|
|
4
|
+
const react_native_1 = require("react-native");
|
|
5
|
+
// Lazy load haptics to avoid errors if not installed
|
|
6
|
+
let Haptics = null;
|
|
7
|
+
try {
|
|
8
|
+
Haptics = require('expo-haptics');
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
console.warn('expo-haptics not installed, haptic feedback will be disabled');
|
|
12
|
+
}
|
|
13
|
+
// โโโ Haptic Feedback Helper โโโ
|
|
14
|
+
const triggerHaptic = (type = 'light') => {
|
|
15
|
+
if (!Haptics) {
|
|
16
|
+
// Silently skip if expo-haptics not available
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
switch (type) {
|
|
21
|
+
case 'light':
|
|
22
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
23
|
+
break;
|
|
24
|
+
case 'medium':
|
|
25
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
|
26
|
+
break;
|
|
27
|
+
case 'heavy':
|
|
28
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
|
|
29
|
+
break;
|
|
30
|
+
case 'success':
|
|
31
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
|
32
|
+
break;
|
|
33
|
+
case 'warning':
|
|
34
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
|
35
|
+
break;
|
|
36
|
+
case 'error':
|
|
37
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.warn('Haptic feedback failed:', error);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.triggerHaptic = triggerHaptic;
|
|
46
|
+
// โโโ Easing Function Mapper โโโ
|
|
47
|
+
const getEasing = (easingType) => {
|
|
48
|
+
switch (easingType) {
|
|
49
|
+
case 'linear':
|
|
50
|
+
return react_native_1.Easing.linear;
|
|
51
|
+
case 'ease-in':
|
|
52
|
+
return react_native_1.Easing.in(react_native_1.Easing.ease);
|
|
53
|
+
case 'ease-out':
|
|
54
|
+
return react_native_1.Easing.out(react_native_1.Easing.ease);
|
|
55
|
+
case 'ease-in-out':
|
|
56
|
+
return react_native_1.Easing.inOut(react_native_1.Easing.ease);
|
|
57
|
+
case 'spring':
|
|
58
|
+
return react_native_1.Easing.elastic(1);
|
|
59
|
+
default:
|
|
60
|
+
return react_native_1.Easing.inOut(react_native_1.Easing.ease);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
exports.getEasing = getEasing;
|
|
64
|
+
const createEntranceAnimationValues = () => ({
|
|
65
|
+
opacity: new react_native_1.Animated.Value(0),
|
|
66
|
+
translateY: new react_native_1.Animated.Value(0),
|
|
67
|
+
translateX: new react_native_1.Animated.Value(0),
|
|
68
|
+
scale: new react_native_1.Animated.Value(1),
|
|
69
|
+
});
|
|
70
|
+
exports.createEntranceAnimationValues = createEntranceAnimationValues;
|
|
71
|
+
const startEntranceAnimation = (config, values, delay = 0) => {
|
|
72
|
+
const duration = config.duration || 400;
|
|
73
|
+
const totalDelay = (config.delay || 0) + delay;
|
|
74
|
+
const easing = (0, exports.getEasing)(config.easing);
|
|
75
|
+
// Set initial values based on animation type
|
|
76
|
+
switch (config.type) {
|
|
77
|
+
case 'fadeIn':
|
|
78
|
+
values.opacity.setValue(0);
|
|
79
|
+
break;
|
|
80
|
+
case 'slideUp':
|
|
81
|
+
values.opacity.setValue(0);
|
|
82
|
+
values.translateY.setValue(30);
|
|
83
|
+
break;
|
|
84
|
+
case 'slideDown':
|
|
85
|
+
values.opacity.setValue(0);
|
|
86
|
+
values.translateY.setValue(-30);
|
|
87
|
+
break;
|
|
88
|
+
case 'slideLeft':
|
|
89
|
+
values.opacity.setValue(0);
|
|
90
|
+
values.translateX.setValue(30);
|
|
91
|
+
break;
|
|
92
|
+
case 'slideRight':
|
|
93
|
+
values.opacity.setValue(0);
|
|
94
|
+
values.translateX.setValue(-30);
|
|
95
|
+
break;
|
|
96
|
+
case 'scaleIn':
|
|
97
|
+
values.opacity.setValue(0);
|
|
98
|
+
values.scale.setValue(0.8);
|
|
99
|
+
break;
|
|
100
|
+
case 'none':
|
|
101
|
+
values.opacity.setValue(1);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Animate to final values
|
|
105
|
+
react_native_1.Animated.parallel([
|
|
106
|
+
react_native_1.Animated.timing(values.opacity, {
|
|
107
|
+
toValue: 1,
|
|
108
|
+
duration,
|
|
109
|
+
delay: totalDelay,
|
|
110
|
+
easing,
|
|
111
|
+
useNativeDriver: true,
|
|
112
|
+
}),
|
|
113
|
+
react_native_1.Animated.timing(values.translateY, {
|
|
114
|
+
toValue: 0,
|
|
115
|
+
duration,
|
|
116
|
+
delay: totalDelay,
|
|
117
|
+
easing,
|
|
118
|
+
useNativeDriver: true,
|
|
119
|
+
}),
|
|
120
|
+
react_native_1.Animated.timing(values.translateX, {
|
|
121
|
+
toValue: 0,
|
|
122
|
+
duration,
|
|
123
|
+
delay: totalDelay,
|
|
124
|
+
easing,
|
|
125
|
+
useNativeDriver: true,
|
|
126
|
+
}),
|
|
127
|
+
react_native_1.Animated.timing(values.scale, {
|
|
128
|
+
toValue: 1,
|
|
129
|
+
duration,
|
|
130
|
+
delay: totalDelay,
|
|
131
|
+
easing,
|
|
132
|
+
useNativeDriver: true,
|
|
133
|
+
}),
|
|
134
|
+
]).start();
|
|
135
|
+
};
|
|
136
|
+
exports.startEntranceAnimation = startEntranceAnimation;
|
|
137
|
+
// โโโ Interactive Animations โโโ
|
|
138
|
+
const startInteractiveAnimation = (config, animatedValue) => {
|
|
139
|
+
const duration = config.duration || 200;
|
|
140
|
+
const intensity = config.intensity || (config.type === 'scale' ? 0.95 : 10);
|
|
141
|
+
// Trigger haptic if enabled
|
|
142
|
+
if (config.haptic && config.hapticType) {
|
|
143
|
+
(0, exports.triggerHaptic)(config.hapticType);
|
|
144
|
+
}
|
|
145
|
+
switch (config.type) {
|
|
146
|
+
case 'scale':
|
|
147
|
+
react_native_1.Animated.sequence([
|
|
148
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
149
|
+
toValue: intensity,
|
|
150
|
+
duration: duration / 2,
|
|
151
|
+
easing: react_native_1.Easing.out(react_native_1.Easing.ease),
|
|
152
|
+
useNativeDriver: true,
|
|
153
|
+
}),
|
|
154
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
155
|
+
toValue: 1,
|
|
156
|
+
duration: duration / 2,
|
|
157
|
+
easing: react_native_1.Easing.in(react_native_1.Easing.ease),
|
|
158
|
+
useNativeDriver: true,
|
|
159
|
+
}),
|
|
160
|
+
]).start();
|
|
161
|
+
break;
|
|
162
|
+
case 'pulse':
|
|
163
|
+
const pulseSequence = react_native_1.Animated.sequence([
|
|
164
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
165
|
+
toValue: 1.05,
|
|
166
|
+
duration: duration,
|
|
167
|
+
easing: react_native_1.Easing.inOut(react_native_1.Easing.ease),
|
|
168
|
+
useNativeDriver: true,
|
|
169
|
+
}),
|
|
170
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
171
|
+
toValue: 1,
|
|
172
|
+
duration: duration,
|
|
173
|
+
easing: react_native_1.Easing.inOut(react_native_1.Easing.ease),
|
|
174
|
+
useNativeDriver: true,
|
|
175
|
+
}),
|
|
176
|
+
]);
|
|
177
|
+
if (config.repeat) {
|
|
178
|
+
react_native_1.Animated.loop(pulseSequence).start();
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
pulseSequence.start();
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
case 'shake':
|
|
185
|
+
react_native_1.Animated.sequence([
|
|
186
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
187
|
+
toValue: intensity,
|
|
188
|
+
duration: duration / 8,
|
|
189
|
+
useNativeDriver: true,
|
|
190
|
+
}),
|
|
191
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
192
|
+
toValue: -intensity,
|
|
193
|
+
duration: duration / 4,
|
|
194
|
+
useNativeDriver: true,
|
|
195
|
+
}),
|
|
196
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
197
|
+
toValue: intensity,
|
|
198
|
+
duration: duration / 4,
|
|
199
|
+
useNativeDriver: true,
|
|
200
|
+
}),
|
|
201
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
202
|
+
toValue: -intensity,
|
|
203
|
+
duration: duration / 4,
|
|
204
|
+
useNativeDriver: true,
|
|
205
|
+
}),
|
|
206
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
207
|
+
toValue: 0,
|
|
208
|
+
duration: duration / 8,
|
|
209
|
+
useNativeDriver: true,
|
|
210
|
+
}),
|
|
211
|
+
]).start();
|
|
212
|
+
break;
|
|
213
|
+
case 'bounce':
|
|
214
|
+
react_native_1.Animated.sequence([
|
|
215
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
216
|
+
toValue: -20,
|
|
217
|
+
duration: duration / 3,
|
|
218
|
+
easing: react_native_1.Easing.out(react_native_1.Easing.ease),
|
|
219
|
+
useNativeDriver: true,
|
|
220
|
+
}),
|
|
221
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
222
|
+
toValue: 5,
|
|
223
|
+
duration: duration / 3,
|
|
224
|
+
easing: react_native_1.Easing.in(react_native_1.Easing.ease),
|
|
225
|
+
useNativeDriver: true,
|
|
226
|
+
}),
|
|
227
|
+
react_native_1.Animated.timing(animatedValue, {
|
|
228
|
+
toValue: 0,
|
|
229
|
+
duration: duration / 3,
|
|
230
|
+
easing: react_native_1.Easing.out(react_native_1.Easing.ease),
|
|
231
|
+
useNativeDriver: true,
|
|
232
|
+
}),
|
|
233
|
+
]).start();
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
exports.startInteractiveAnimation = startInteractiveAnimation;
|
|
238
|
+
const shouldTriggerHaptic = (index, frequency) => {
|
|
239
|
+
switch (frequency) {
|
|
240
|
+
case 'every':
|
|
241
|
+
return true;
|
|
242
|
+
case 'every-2':
|
|
243
|
+
return index % 2 === 0;
|
|
244
|
+
case 'every-3':
|
|
245
|
+
return index % 3 === 0;
|
|
246
|
+
case 'every-5':
|
|
247
|
+
return index % 5 === 0;
|
|
248
|
+
default:
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
exports.shouldTriggerHaptic = shouldTriggerHaptic;
|
|
@@ -8,6 +8,11 @@ interface ElementRendererProps {
|
|
|
8
8
|
onDismiss?: () => void;
|
|
9
9
|
variables?: Record<string, any>;
|
|
10
10
|
onSetVariable?: (name: string, value: any) => void;
|
|
11
|
+
assets?: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
data: string;
|
|
15
|
+
}>;
|
|
11
16
|
}
|
|
12
17
|
export declare const ElementRenderer: React.FC<ElementRendererProps>;
|
|
13
18
|
export {};
|