noboarding 1.0.3-beta → 1.0.7
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 +231 -40
- package/lib/types.d.ts +46 -0
- package/package.json +4 -2
- package/src/OnboardingFlow.tsx +19 -0
- package/src/animationUtils.ts +292 -0
- package/src/components/ElementRenderer.tsx +287 -22
- package/src/types.ts +51 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { Animated, Easing } from 'react-native';
|
|
2
|
+
import type {
|
|
3
|
+
EntranceAnimation,
|
|
4
|
+
InteractiveAnimation,
|
|
5
|
+
TextAnimation,
|
|
6
|
+
HapticType
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
// Lazy load haptics to avoid errors if not installed
|
|
10
|
+
let Haptics: any = null;
|
|
11
|
+
try {
|
|
12
|
+
Haptics = require('expo-haptics');
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.warn('expo-haptics not installed, haptic feedback will be disabled');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ─── Haptic Feedback Helper ───
|
|
18
|
+
|
|
19
|
+
export const triggerHaptic = (type: HapticType = 'light') => {
|
|
20
|
+
if (!Haptics) {
|
|
21
|
+
// Silently skip if expo-haptics not available
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case 'light':
|
|
28
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
29
|
+
break;
|
|
30
|
+
case 'medium':
|
|
31
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
|
32
|
+
break;
|
|
33
|
+
case 'heavy':
|
|
34
|
+
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
|
|
35
|
+
break;
|
|
36
|
+
case 'success':
|
|
37
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
|
|
38
|
+
break;
|
|
39
|
+
case 'warning':
|
|
40
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
|
|
41
|
+
break;
|
|
42
|
+
case 'error':
|
|
43
|
+
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.warn('Haptic feedback failed:', error);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ─── Easing Function Mapper ───
|
|
52
|
+
|
|
53
|
+
export const getEasing = (easingType?: string) => {
|
|
54
|
+
switch (easingType) {
|
|
55
|
+
case 'linear':
|
|
56
|
+
return Easing.linear;
|
|
57
|
+
case 'ease-in':
|
|
58
|
+
return Easing.in(Easing.ease);
|
|
59
|
+
case 'ease-out':
|
|
60
|
+
return Easing.out(Easing.ease);
|
|
61
|
+
case 'ease-in-out':
|
|
62
|
+
return Easing.inOut(Easing.ease);
|
|
63
|
+
case 'spring':
|
|
64
|
+
return Easing.elastic(1);
|
|
65
|
+
default:
|
|
66
|
+
return Easing.inOut(Easing.ease);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ─── Entrance Animations ───
|
|
71
|
+
|
|
72
|
+
export interface EntranceAnimationValues {
|
|
73
|
+
opacity: Animated.Value;
|
|
74
|
+
translateY: Animated.Value;
|
|
75
|
+
translateX: Animated.Value;
|
|
76
|
+
scale: Animated.Value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const createEntranceAnimationValues = (): EntranceAnimationValues => ({
|
|
80
|
+
opacity: new Animated.Value(0),
|
|
81
|
+
translateY: new Animated.Value(0),
|
|
82
|
+
translateX: new Animated.Value(0),
|
|
83
|
+
scale: new Animated.Value(1),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const startEntranceAnimation = (
|
|
87
|
+
config: EntranceAnimation,
|
|
88
|
+
values: EntranceAnimationValues,
|
|
89
|
+
delay: number = 0
|
|
90
|
+
): void => {
|
|
91
|
+
const duration = config.duration || 400;
|
|
92
|
+
const totalDelay = (config.delay || 0) + delay;
|
|
93
|
+
const easing = getEasing(config.easing);
|
|
94
|
+
|
|
95
|
+
// Set initial values based on animation type
|
|
96
|
+
switch (config.type) {
|
|
97
|
+
case 'fadeIn':
|
|
98
|
+
values.opacity.setValue(0);
|
|
99
|
+
break;
|
|
100
|
+
case 'slideUp':
|
|
101
|
+
values.opacity.setValue(0);
|
|
102
|
+
values.translateY.setValue(30);
|
|
103
|
+
break;
|
|
104
|
+
case 'slideDown':
|
|
105
|
+
values.opacity.setValue(0);
|
|
106
|
+
values.translateY.setValue(-30);
|
|
107
|
+
break;
|
|
108
|
+
case 'slideLeft':
|
|
109
|
+
values.opacity.setValue(0);
|
|
110
|
+
values.translateX.setValue(30);
|
|
111
|
+
break;
|
|
112
|
+
case 'slideRight':
|
|
113
|
+
values.opacity.setValue(0);
|
|
114
|
+
values.translateX.setValue(-30);
|
|
115
|
+
break;
|
|
116
|
+
case 'scaleIn':
|
|
117
|
+
values.opacity.setValue(0);
|
|
118
|
+
values.scale.setValue(0.8);
|
|
119
|
+
break;
|
|
120
|
+
case 'none':
|
|
121
|
+
values.opacity.setValue(1);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Animate to final values
|
|
126
|
+
Animated.parallel([
|
|
127
|
+
Animated.timing(values.opacity, {
|
|
128
|
+
toValue: 1,
|
|
129
|
+
duration,
|
|
130
|
+
delay: totalDelay,
|
|
131
|
+
easing,
|
|
132
|
+
useNativeDriver: true,
|
|
133
|
+
}),
|
|
134
|
+
Animated.timing(values.translateY, {
|
|
135
|
+
toValue: 0,
|
|
136
|
+
duration,
|
|
137
|
+
delay: totalDelay,
|
|
138
|
+
easing,
|
|
139
|
+
useNativeDriver: true,
|
|
140
|
+
}),
|
|
141
|
+
Animated.timing(values.translateX, {
|
|
142
|
+
toValue: 0,
|
|
143
|
+
duration,
|
|
144
|
+
delay: totalDelay,
|
|
145
|
+
easing,
|
|
146
|
+
useNativeDriver: true,
|
|
147
|
+
}),
|
|
148
|
+
Animated.timing(values.scale, {
|
|
149
|
+
toValue: 1,
|
|
150
|
+
duration,
|
|
151
|
+
delay: totalDelay,
|
|
152
|
+
easing,
|
|
153
|
+
useNativeDriver: true,
|
|
154
|
+
}),
|
|
155
|
+
]).start();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ─── Interactive Animations ───
|
|
159
|
+
|
|
160
|
+
export const startInteractiveAnimation = (
|
|
161
|
+
config: InteractiveAnimation,
|
|
162
|
+
animatedValue: Animated.Value
|
|
163
|
+
): void => {
|
|
164
|
+
const duration = config.duration || 200;
|
|
165
|
+
const intensity = config.intensity || (config.type === 'scale' ? 0.95 : 10);
|
|
166
|
+
|
|
167
|
+
// Trigger haptic if enabled
|
|
168
|
+
if (config.haptic && config.hapticType) {
|
|
169
|
+
triggerHaptic(config.hapticType);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
switch (config.type) {
|
|
173
|
+
case 'scale':
|
|
174
|
+
Animated.sequence([
|
|
175
|
+
Animated.timing(animatedValue, {
|
|
176
|
+
toValue: intensity,
|
|
177
|
+
duration: duration / 2,
|
|
178
|
+
easing: Easing.out(Easing.ease),
|
|
179
|
+
useNativeDriver: true,
|
|
180
|
+
}),
|
|
181
|
+
Animated.timing(animatedValue, {
|
|
182
|
+
toValue: 1,
|
|
183
|
+
duration: duration / 2,
|
|
184
|
+
easing: Easing.in(Easing.ease),
|
|
185
|
+
useNativeDriver: true,
|
|
186
|
+
}),
|
|
187
|
+
]).start();
|
|
188
|
+
break;
|
|
189
|
+
|
|
190
|
+
case 'pulse':
|
|
191
|
+
const pulseSequence = Animated.sequence([
|
|
192
|
+
Animated.timing(animatedValue, {
|
|
193
|
+
toValue: 1.05,
|
|
194
|
+
duration: duration,
|
|
195
|
+
easing: Easing.inOut(Easing.ease),
|
|
196
|
+
useNativeDriver: true,
|
|
197
|
+
}),
|
|
198
|
+
Animated.timing(animatedValue, {
|
|
199
|
+
toValue: 1,
|
|
200
|
+
duration: duration,
|
|
201
|
+
easing: Easing.inOut(Easing.ease),
|
|
202
|
+
useNativeDriver: true,
|
|
203
|
+
}),
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
if (config.repeat) {
|
|
207
|
+
Animated.loop(pulseSequence).start();
|
|
208
|
+
} else {
|
|
209
|
+
pulseSequence.start();
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case 'shake':
|
|
214
|
+
Animated.sequence([
|
|
215
|
+
Animated.timing(animatedValue, {
|
|
216
|
+
toValue: intensity,
|
|
217
|
+
duration: duration / 8,
|
|
218
|
+
useNativeDriver: true,
|
|
219
|
+
}),
|
|
220
|
+
Animated.timing(animatedValue, {
|
|
221
|
+
toValue: -intensity,
|
|
222
|
+
duration: duration / 4,
|
|
223
|
+
useNativeDriver: true,
|
|
224
|
+
}),
|
|
225
|
+
Animated.timing(animatedValue, {
|
|
226
|
+
toValue: intensity,
|
|
227
|
+
duration: duration / 4,
|
|
228
|
+
useNativeDriver: true,
|
|
229
|
+
}),
|
|
230
|
+
Animated.timing(animatedValue, {
|
|
231
|
+
toValue: -intensity,
|
|
232
|
+
duration: duration / 4,
|
|
233
|
+
useNativeDriver: true,
|
|
234
|
+
}),
|
|
235
|
+
Animated.timing(animatedValue, {
|
|
236
|
+
toValue: 0,
|
|
237
|
+
duration: duration / 8,
|
|
238
|
+
useNativeDriver: true,
|
|
239
|
+
}),
|
|
240
|
+
]).start();
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'bounce':
|
|
244
|
+
Animated.sequence([
|
|
245
|
+
Animated.timing(animatedValue, {
|
|
246
|
+
toValue: -20,
|
|
247
|
+
duration: duration / 3,
|
|
248
|
+
easing: Easing.out(Easing.ease),
|
|
249
|
+
useNativeDriver: true,
|
|
250
|
+
}),
|
|
251
|
+
Animated.timing(animatedValue, {
|
|
252
|
+
toValue: 5,
|
|
253
|
+
duration: duration / 3,
|
|
254
|
+
easing: Easing.in(Easing.ease),
|
|
255
|
+
useNativeDriver: true,
|
|
256
|
+
}),
|
|
257
|
+
Animated.timing(animatedValue, {
|
|
258
|
+
toValue: 0,
|
|
259
|
+
duration: duration / 3,
|
|
260
|
+
easing: Easing.out(Easing.ease),
|
|
261
|
+
useNativeDriver: true,
|
|
262
|
+
}),
|
|
263
|
+
]).start();
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// ─── Typewriter Text Animation ───
|
|
269
|
+
|
|
270
|
+
export interface TypewriterState {
|
|
271
|
+
displayedText: string;
|
|
272
|
+
currentIndex: number;
|
|
273
|
+
isComplete: boolean;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export const shouldTriggerHaptic = (
|
|
277
|
+
index: number,
|
|
278
|
+
frequency: string
|
|
279
|
+
): boolean => {
|
|
280
|
+
switch (frequency) {
|
|
281
|
+
case 'every':
|
|
282
|
+
return true;
|
|
283
|
+
case 'every-2':
|
|
284
|
+
return index % 2 === 0;
|
|
285
|
+
case 'every-3':
|
|
286
|
+
return index % 3 === 0;
|
|
287
|
+
case 'every-5':
|
|
288
|
+
return index % 5 === 0;
|
|
289
|
+
default:
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
};
|