@rocapine/react-native-onboarding-ui 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/dist/UI/Components/CircularProgress.d.ts +8 -0
- package/dist/UI/Components/CircularProgress.d.ts.map +1 -0
- package/dist/UI/Components/CircularProgress.js +104 -0
- package/dist/UI/Components/CircularProgress.js.map +1 -0
- package/dist/UI/Components/ProgressBar.d.ts +12 -0
- package/dist/UI/Components/ProgressBar.d.ts.map +1 -0
- package/dist/UI/Components/ProgressBar.js +121 -0
- package/dist/UI/Components/ProgressBar.js.map +1 -0
- package/dist/UI/Components/StaggeredTextList.d.ts +11 -0
- package/dist/UI/Components/StaggeredTextList.d.ts.map +1 -0
- package/dist/UI/Components/StaggeredTextList.js +111 -0
- package/dist/UI/Components/StaggeredTextList.js.map +1 -0
- package/dist/UI/Components/index.d.ts +4 -0
- package/dist/UI/Components/index.d.ts.map +1 -0
- package/dist/UI/Components/index.js +20 -0
- package/dist/UI/Components/index.js.map +1 -0
- package/dist/UI/ErrorBoundary/ErrorBoundary.d.ts +19 -0
- package/dist/UI/ErrorBoundary/ErrorBoundary.d.ts.map +1 -0
- package/dist/UI/ErrorBoundary/ErrorBoundary.js +123 -0
- package/dist/UI/ErrorBoundary/ErrorBoundary.js.map +1 -0
- package/dist/UI/ErrorBoundary/index.d.ts +3 -0
- package/dist/UI/ErrorBoundary/index.d.ts.map +1 -0
- package/dist/UI/ErrorBoundary/index.js +8 -0
- package/dist/UI/ErrorBoundary/index.js.map +1 -0
- package/dist/UI/ErrorBoundary/withErrorBoundary.d.ts +6 -0
- package/dist/UI/ErrorBoundary/withErrorBoundary.d.ts.map +1 -0
- package/dist/UI/ErrorBoundary/withErrorBoundary.js +13 -0
- package/dist/UI/ErrorBoundary/withErrorBoundary.js.map +1 -0
- package/dist/UI/OnboardingPage.d.ts +16 -0
- package/dist/UI/OnboardingPage.d.ts.map +1 -0
- package/dist/UI/OnboardingPage.js +38 -0
- package/dist/UI/OnboardingPage.js.map +1 -0
- package/dist/UI/Pages/Carousel/Renderer.d.ts +13 -0
- package/dist/UI/Pages/Carousel/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/Carousel/Renderer.js +121 -0
- package/dist/UI/Pages/Carousel/Renderer.js.map +1 -0
- package/dist/UI/Pages/Carousel/index.d.ts +3 -0
- package/dist/UI/Pages/Carousel/index.d.ts.map +1 -0
- package/dist/UI/Pages/Carousel/index.js +19 -0
- package/dist/UI/Pages/Carousel/index.js.map +1 -0
- package/dist/UI/Pages/Carousel/types.d.ts +32 -0
- package/dist/UI/Pages/Carousel/types.d.ts.map +1 -0
- package/dist/UI/Pages/Carousel/types.js +24 -0
- package/dist/UI/Pages/Carousel/types.js.map +1 -0
- package/dist/UI/Pages/Commitment/Renderer.d.ts +13 -0
- package/dist/UI/Pages/Commitment/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/Commitment/Renderer.js +173 -0
- package/dist/UI/Pages/Commitment/Renderer.js.map +1 -0
- package/dist/UI/Pages/Commitment/index.d.ts +3 -0
- package/dist/UI/Pages/Commitment/index.d.ts.map +1 -0
- package/dist/UI/Pages/Commitment/index.js +19 -0
- package/dist/UI/Pages/Commitment/index.js.map +1 -0
- package/dist/UI/Pages/Commitment/types.d.ts +41 -0
- package/dist/UI/Pages/Commitment/types.d.ts.map +1 -0
- package/dist/UI/Pages/Commitment/types.js +27 -0
- package/dist/UI/Pages/Commitment/types.js.map +1 -0
- package/dist/UI/Pages/Loader/Renderer.d.ts +10 -0
- package/dist/UI/Pages/Loader/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/Loader/Renderer.js +215 -0
- package/dist/UI/Pages/Loader/Renderer.js.map +1 -0
- package/dist/UI/Pages/Loader/index.d.ts +3 -0
- package/dist/UI/Pages/Loader/index.d.ts.map +1 -0
- package/dist/UI/Pages/Loader/index.js +19 -0
- package/dist/UI/Pages/Loader/index.js.map +1 -0
- package/dist/UI/Pages/Loader/types.d.ts +57 -0
- package/dist/UI/Pages/Loader/types.d.ts.map +1 -0
- package/dist/UI/Pages/Loader/types.js +30 -0
- package/dist/UI/Pages/Loader/types.js.map +1 -0
- package/dist/UI/Pages/MediaContent/Renderer.d.ts +13 -0
- package/dist/UI/Pages/MediaContent/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/MediaContent/Renderer.js +76 -0
- package/dist/UI/Pages/MediaContent/Renderer.js.map +1 -0
- package/dist/UI/Pages/MediaContent/index.d.ts +3 -0
- package/dist/UI/Pages/MediaContent/index.d.ts.map +1 -0
- package/dist/UI/Pages/MediaContent/index.js +19 -0
- package/dist/UI/Pages/MediaContent/index.js.map +1 -0
- package/dist/UI/Pages/MediaContent/types.d.ts +44 -0
- package/dist/UI/Pages/MediaContent/types.d.ts.map +1 -0
- package/dist/UI/Pages/MediaContent/types.js +22 -0
- package/dist/UI/Pages/MediaContent/types.js.map +1 -0
- package/dist/UI/Pages/Picker/Renderer.d.ts +13 -0
- package/dist/UI/Pages/Picker/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/Picker/Renderer.js +268 -0
- package/dist/UI/Pages/Picker/Renderer.js.map +1 -0
- package/dist/UI/Pages/Picker/index.d.ts +3 -0
- package/dist/UI/Pages/Picker/index.d.ts.map +1 -0
- package/dist/UI/Pages/Picker/index.js +19 -0
- package/dist/UI/Pages/Picker/index.js.map +1 -0
- package/dist/UI/Pages/Picker/types.d.ts +49 -0
- package/dist/UI/Pages/Picker/types.d.ts.map +1 -0
- package/dist/UI/Pages/Picker/types.js +30 -0
- package/dist/UI/Pages/Picker/types.js.map +1 -0
- package/dist/UI/Pages/Question/Renderer.d.ts +18 -0
- package/dist/UI/Pages/Question/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/Question/Renderer.js +128 -0
- package/dist/UI/Pages/Question/Renderer.js.map +1 -0
- package/dist/UI/Pages/Question/components.d.ts +57 -0
- package/dist/UI/Pages/Question/components.d.ts.map +1 -0
- package/dist/UI/Pages/Question/components.js +57 -0
- package/dist/UI/Pages/Question/components.js.map +1 -0
- package/dist/UI/Pages/Question/index.d.ts +4 -0
- package/dist/UI/Pages/Question/index.d.ts.map +1 -0
- package/dist/UI/Pages/Question/index.js +20 -0
- package/dist/UI/Pages/Question/index.js.map +1 -0
- package/dist/UI/Pages/Question/types.d.ts +47 -0
- package/dist/UI/Pages/Question/types.d.ts.map +1 -0
- package/dist/UI/Pages/Question/types.js +28 -0
- package/dist/UI/Pages/Question/types.js.map +1 -0
- package/dist/UI/Pages/Ratings/Renderer.d.ts +13 -0
- package/dist/UI/Pages/Ratings/Renderer.d.ts.map +1 -0
- package/dist/UI/Pages/Ratings/Renderer.js +201 -0
- package/dist/UI/Pages/Ratings/Renderer.js.map +1 -0
- package/dist/UI/Pages/Ratings/index.d.ts +3 -0
- package/dist/UI/Pages/Ratings/index.d.ts.map +1 -0
- package/dist/UI/Pages/Ratings/index.js +19 -0
- package/dist/UI/Pages/Ratings/index.js.map +1 -0
- package/dist/UI/Pages/Ratings/types.d.ts +32 -0
- package/dist/UI/Pages/Ratings/types.d.ts.map +1 -0
- package/dist/UI/Pages/Ratings/types.js +25 -0
- package/dist/UI/Pages/Ratings/types.js.map +1 -0
- package/dist/UI/Pages/index.d.ts +9 -0
- package/dist/UI/Pages/index.d.ts.map +1 -0
- package/dist/UI/Pages/index.js +26 -0
- package/dist/UI/Pages/index.js.map +1 -0
- package/dist/UI/Pages/types.d.ts +19 -0
- package/dist/UI/Pages/types.d.ts.map +1 -0
- package/dist/UI/Pages/types.js +25 -0
- package/dist/UI/Pages/types.js.map +1 -0
- package/dist/UI/Provider/OnboardingProgressProvider.d.ts +18 -0
- package/dist/UI/Provider/OnboardingProgressProvider.d.ts.map +1 -0
- package/dist/UI/Provider/OnboardingProgressProvider.js +23 -0
- package/dist/UI/Provider/OnboardingProgressProvider.js.map +1 -0
- package/dist/UI/Provider/index.d.ts +2 -0
- package/dist/UI/Provider/index.d.ts.map +1 -0
- package/dist/UI/Provider/index.js +7 -0
- package/dist/UI/Provider/index.js.map +1 -0
- package/dist/UI/Templates/OnboardingTemplate.d.ts +15 -0
- package/dist/UI/Templates/OnboardingTemplate.d.ts.map +1 -0
- package/dist/UI/Templates/OnboardingTemplate.js +48 -0
- package/dist/UI/Templates/OnboardingTemplate.js.map +1 -0
- package/dist/UI/Templates/index.d.ts +2 -0
- package/dist/UI/Templates/index.d.ts.map +1 -0
- package/dist/UI/Templates/index.js +6 -0
- package/dist/UI/Templates/index.js.map +1 -0
- package/dist/UI/Theme/ThemeProvider.d.ts +27 -0
- package/dist/UI/Theme/ThemeProvider.d.ts.map +1 -0
- package/dist/UI/Theme/ThemeProvider.js +49 -0
- package/dist/UI/Theme/ThemeProvider.js.map +1 -0
- package/dist/UI/Theme/defaultTheme.d.ts +7 -0
- package/dist/UI/Theme/defaultTheme.d.ts.map +1 -0
- package/dist/UI/Theme/defaultTheme.js +14 -0
- package/dist/UI/Theme/defaultTheme.js.map +1 -0
- package/dist/UI/Theme/helpers.d.ts +12 -0
- package/dist/UI/Theme/helpers.d.ts.map +1 -0
- package/dist/UI/Theme/helpers.js +21 -0
- package/dist/UI/Theme/helpers.js.map +1 -0
- package/dist/UI/Theme/index.d.ts +8 -0
- package/dist/UI/Theme/index.d.ts.map +1 -0
- package/dist/UI/Theme/index.js +24 -0
- package/dist/UI/Theme/index.js.map +1 -0
- package/dist/UI/Theme/token.d.ts +107 -0
- package/dist/UI/Theme/token.d.ts.map +1 -0
- package/dist/UI/Theme/token.js +108 -0
- package/dist/UI/Theme/token.js.map +1 -0
- package/dist/UI/Theme/tokens/darkTokens.d.ts +24 -0
- package/dist/UI/Theme/tokens/darkTokens.d.ts.map +1 -0
- package/dist/UI/Theme/tokens/darkTokens.js +27 -0
- package/dist/UI/Theme/tokens/darkTokens.js.map +1 -0
- package/dist/UI/Theme/tokens/index.d.ts +4 -0
- package/dist/UI/Theme/tokens/index.d.ts.map +1 -0
- package/dist/UI/Theme/tokens/index.js +20 -0
- package/dist/UI/Theme/tokens/index.js.map +1 -0
- package/dist/UI/Theme/tokens/lightTokens.d.ts +24 -0
- package/dist/UI/Theme/tokens/lightTokens.d.ts.map +1 -0
- package/dist/UI/Theme/tokens/lightTokens.js +27 -0
- package/dist/UI/Theme/tokens/lightTokens.js.map +1 -0
- package/dist/UI/Theme/tokens/typography.d.ts +65 -0
- package/dist/UI/Theme/tokens/typography.d.ts.map +1 -0
- package/dist/UI/Theme/tokens/typography.js +68 -0
- package/dist/UI/Theme/tokens/typography.js.map +1 -0
- package/dist/UI/Theme/types.d.ts +65 -0
- package/dist/UI/Theme/types.d.ts.map +1 -0
- package/dist/UI/Theme/types.js +3 -0
- package/dist/UI/Theme/types.js.map +1 -0
- package/dist/UI/Theme/useTheme.d.ts +7 -0
- package/dist/UI/Theme/useTheme.d.ts.map +1 -0
- package/dist/UI/Theme/useTheme.js +23 -0
- package/dist/UI/Theme/useTheme.js.map +1 -0
- package/dist/UI/Theme/utils.d.ts +12 -0
- package/dist/UI/Theme/utils.d.ts.map +1 -0
- package/dist/UI/Theme/utils.js +55 -0
- package/dist/UI/Theme/utils.js.map +1 -0
- package/dist/UI/index.d.ts +8 -0
- package/dist/UI/index.d.ts.map +1 -0
- package/dist/UI/index.js +24 -0
- package/dist/UI/index.js.map +1 -0
- package/dist/UI/types.d.ts +23 -0
- package/dist/UI/types.d.ts.map +1 -0
- package/dist/UI/types.js +3 -0
- package/dist/UI/types.js.map +1 -0
- package/dist/assets/laurel-left.png +0 -0
- package/dist/assets/laurel-right.png +0 -0
- package/dist/assets/star-filled.png +0 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/provider/CustomComponentsContext.d.ts +57 -0
- package/dist/provider/CustomComponentsContext.d.ts.map +1 -0
- package/dist/provider/CustomComponentsContext.js +19 -0
- package/dist/provider/CustomComponentsContext.js.map +1 -0
- package/dist/provider/OnboardingUIProvider.d.ts +44 -0
- package/dist/provider/OnboardingUIProvider.d.ts.map +1 -0
- package/dist/provider/OnboardingUIProvider.js +33 -0
- package/dist/provider/OnboardingUIProvider.js.map +1 -0
- package/dist/provider/OnboardingUIProvider.old.d.ts +60 -0
- package/dist/provider/OnboardingUIProvider.old.d.ts.map +1 -0
- package/dist/provider/OnboardingUIProvider.old.js +53 -0
- package/dist/provider/OnboardingUIProvider.old.js.map +1 -0
- package/package.json +77 -0
- package/src/UI/Components/CircularProgress.tsx +146 -0
- package/src/UI/Components/ProgressBar.tsx +143 -0
- package/src/UI/Components/StaggeredTextList.tsx +152 -0
- package/src/UI/Components/index.ts +3 -0
- package/src/UI/ErrorBoundary/ErrorBoundary.tsx +181 -0
- package/src/UI/ErrorBoundary/README.md +71 -0
- package/src/UI/ErrorBoundary/index.ts +2 -0
- package/src/UI/ErrorBoundary/withErrorBoundary.tsx +19 -0
- package/src/UI/OnboardingPage.tsx +53 -0
- package/src/UI/Pages/Carousel/Renderer.tsx +210 -0
- package/src/UI/Pages/Carousel/index.ts +2 -0
- package/src/UI/Pages/Carousel/types.ts +26 -0
- package/src/UI/Pages/Commitment/Renderer.tsx +312 -0
- package/src/UI/Pages/Commitment/index.ts +2 -0
- package/src/UI/Pages/Commitment/types.ts +28 -0
- package/src/UI/Pages/Loader/Renderer.tsx +417 -0
- package/src/UI/Pages/Loader/index.ts +2 -0
- package/src/UI/Pages/Loader/types.ts +32 -0
- package/src/UI/Pages/MediaContent/Renderer.tsx +130 -0
- package/src/UI/Pages/MediaContent/index.ts +2 -0
- package/src/UI/Pages/MediaContent/types.ts +26 -0
- package/src/UI/Pages/Picker/Renderer.tsx +618 -0
- package/src/UI/Pages/Picker/index.ts +2 -0
- package/src/UI/Pages/Picker/types.ts +34 -0
- package/src/UI/Pages/Question/Renderer.tsx +208 -0
- package/src/UI/Pages/Question/components.tsx +130 -0
- package/src/UI/Pages/Question/index.ts +3 -0
- package/src/UI/Pages/Question/types.ts +29 -0
- package/src/UI/Pages/Ratings/Renderer.tsx +282 -0
- package/src/UI/Pages/Ratings/index.ts +2 -0
- package/src/UI/Pages/Ratings/types.ts +22 -0
- package/src/UI/Pages/index.ts +10 -0
- package/src/UI/Pages/types.ts +25 -0
- package/src/UI/Provider/OnboardingProgressProvider.tsx +40 -0
- package/src/UI/Provider/index.ts +1 -0
- package/src/UI/Templates/OnboardingTemplate.tsx +86 -0
- package/src/UI/Templates/index.ts +1 -0
- package/src/UI/Theme/ThemeProvider.tsx +100 -0
- package/src/UI/Theme/defaultTheme.ts +12 -0
- package/src/UI/Theme/helpers.ts +24 -0
- package/src/UI/Theme/index.ts +7 -0
- package/src/UI/Theme/token.ts +106 -0
- package/src/UI/Theme/tokens/darkTokens.ts +25 -0
- package/src/UI/Theme/tokens/index.ts +3 -0
- package/src/UI/Theme/tokens/lightTokens.ts +25 -0
- package/src/UI/Theme/tokens/typography.ts +66 -0
- package/src/UI/Theme/types.ts +72 -0
- package/src/UI/Theme/useTheme.ts +22 -0
- package/src/UI/Theme/utils.ts +67 -0
- package/src/UI/index.ts +7 -0
- package/src/UI/types.ts +41 -0
- package/src/assets/laurel-left.png +0 -0
- package/src/assets/laurel-right.png +0 -0
- package/src/assets/star-filled.png +0 -0
- package/src/index.ts +28 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withTiming,
|
|
7
|
+
} from "react-native-reanimated";
|
|
8
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
9
|
+
import { ChevronLeft } from "lucide-react-native";
|
|
10
|
+
import { useRouter } from "expo-router";
|
|
11
|
+
import { defaultTheme, Theme } from "../Theme";
|
|
12
|
+
|
|
13
|
+
interface ProgressBarProps {
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
progressColor?: string;
|
|
16
|
+
progressPercentage: number;
|
|
17
|
+
theme?: Theme;
|
|
18
|
+
isProgressBarVisible?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ProgressBar: React.FC<ProgressBarProps> = ({
|
|
22
|
+
backgroundColor,
|
|
23
|
+
progressColor,
|
|
24
|
+
progressPercentage = 0,
|
|
25
|
+
theme = defaultTheme,
|
|
26
|
+
isProgressBarVisible = true,
|
|
27
|
+
}) => {
|
|
28
|
+
const animated = true;
|
|
29
|
+
const router = useRouter();
|
|
30
|
+
const { top } = useSafeAreaInsets();
|
|
31
|
+
|
|
32
|
+
const height = 12
|
|
33
|
+
// Use Reanimated shared value for smooth animations
|
|
34
|
+
const progress = useSharedValue(0);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (animated) {
|
|
38
|
+
progress.value = withTiming(progressPercentage, {
|
|
39
|
+
duration: 300,
|
|
40
|
+
});
|
|
41
|
+
} else {
|
|
42
|
+
progress.value = progressPercentage;
|
|
43
|
+
}
|
|
44
|
+
}, [progressPercentage, animated]);
|
|
45
|
+
|
|
46
|
+
// Animated style for the progress bar
|
|
47
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
48
|
+
return {
|
|
49
|
+
width: `${progress.value}%`,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Use theme colors with fallback to props
|
|
54
|
+
const trackBgColor = backgroundColor || theme.colors.neutral.lower;
|
|
55
|
+
const barColor = progressColor || theme.colors.primary;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
isProgressBarVisible && (
|
|
59
|
+
<View style={[styles.container, { paddingTop: top }]}>
|
|
60
|
+
<View style={styles.progressBarContainer}>
|
|
61
|
+
{/* Left section: Back button */}
|
|
62
|
+
<View style={styles.backButtonSection}>
|
|
63
|
+
{router.canGoBack() && (
|
|
64
|
+
<TouchableOpacity
|
|
65
|
+
onPress={() => router.back()}
|
|
66
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
67
|
+
style={styles.backButton}
|
|
68
|
+
>
|
|
69
|
+
<ChevronLeft
|
|
70
|
+
size={24}
|
|
71
|
+
color={theme.colors.text.primary}
|
|
72
|
+
strokeWidth={2}
|
|
73
|
+
/>
|
|
74
|
+
</TouchableOpacity>
|
|
75
|
+
)}
|
|
76
|
+
</View>
|
|
77
|
+
|
|
78
|
+
{/* Center section: Progress bar */}
|
|
79
|
+
<View style={styles.progressSection}>
|
|
80
|
+
<View
|
|
81
|
+
style={[styles.track, { height, backgroundColor: trackBgColor }]}
|
|
82
|
+
>
|
|
83
|
+
<Animated.View
|
|
84
|
+
style={[
|
|
85
|
+
styles.progress,
|
|
86
|
+
{
|
|
87
|
+
height,
|
|
88
|
+
backgroundColor: barColor,
|
|
89
|
+
},
|
|
90
|
+
animatedStyle,
|
|
91
|
+
]}
|
|
92
|
+
/>
|
|
93
|
+
</View>
|
|
94
|
+
</View>
|
|
95
|
+
|
|
96
|
+
{/* Right section: Spacer */}
|
|
97
|
+
<View style={styles.spacerSection} />
|
|
98
|
+
</View>
|
|
99
|
+
</View>
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const styles = StyleSheet.create({
|
|
105
|
+
container: {
|
|
106
|
+
position: "absolute",
|
|
107
|
+
top: 0,
|
|
108
|
+
left: 0,
|
|
109
|
+
right: 0,
|
|
110
|
+
zIndex: 10,
|
|
111
|
+
justifyContent: "center",
|
|
112
|
+
paddingBottom: 24,
|
|
113
|
+
},
|
|
114
|
+
progressBarContainer: {
|
|
115
|
+
width: "100%",
|
|
116
|
+
flexDirection: "row",
|
|
117
|
+
alignItems: "center",
|
|
118
|
+
gap: 16,
|
|
119
|
+
paddingHorizontal: 16,
|
|
120
|
+
},
|
|
121
|
+
backButtonSection: {
|
|
122
|
+
flex: 1,
|
|
123
|
+
alignItems: "flex-start",
|
|
124
|
+
},
|
|
125
|
+
backButton: {
|
|
126
|
+
padding: 4,
|
|
127
|
+
},
|
|
128
|
+
progressSection: {
|
|
129
|
+
flex: 5,
|
|
130
|
+
alignItems: "flex-end",
|
|
131
|
+
},
|
|
132
|
+
spacerSection: {
|
|
133
|
+
flex: 1,
|
|
134
|
+
},
|
|
135
|
+
track: {
|
|
136
|
+
width: "100%",
|
|
137
|
+
borderRadius: 10,
|
|
138
|
+
overflow: "hidden",
|
|
139
|
+
},
|
|
140
|
+
progress: {
|
|
141
|
+
borderRadius: 10,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withTiming,
|
|
7
|
+
withSequence,
|
|
8
|
+
Easing,
|
|
9
|
+
interpolateColor,
|
|
10
|
+
SharedValue,
|
|
11
|
+
} from "react-native-reanimated";
|
|
12
|
+
import { useTheme } from "../Theme/useTheme";
|
|
13
|
+
import { Theme } from "../Theme/types";
|
|
14
|
+
|
|
15
|
+
type TextItem = {
|
|
16
|
+
label: string;
|
|
17
|
+
completed: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type StaggeredTextListProps = {
|
|
21
|
+
items: TextItem[];
|
|
22
|
+
duration: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const StaggeredTextList = ({
|
|
26
|
+
items,
|
|
27
|
+
duration,
|
|
28
|
+
}: StaggeredTextListProps) => {
|
|
29
|
+
const { theme } = useTheme();
|
|
30
|
+
const styles = createStyles(theme);
|
|
31
|
+
|
|
32
|
+
// Create a shared value to track the current active index
|
|
33
|
+
const activeIndex = useSharedValue(0);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
// Reset animation
|
|
37
|
+
activeIndex.value = 0;
|
|
38
|
+
|
|
39
|
+
// Animate sequentially through each text
|
|
40
|
+
const animateSequentially = async () => {
|
|
41
|
+
for (let i = 0; i < items.length; i++) {
|
|
42
|
+
activeIndex.value = i;
|
|
43
|
+
|
|
44
|
+
// Wait for duration before moving to next
|
|
45
|
+
// Last item stays longer
|
|
46
|
+
const waitTime = i === items.length - 1 ? duration * 1.5 : duration;
|
|
47
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
animateSequentially();
|
|
52
|
+
}, [items, duration, activeIndex]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<View style={styles.container}>
|
|
56
|
+
{items.map((item, index) => (
|
|
57
|
+
<AnimatedTextItem
|
|
58
|
+
key={index}
|
|
59
|
+
item={item}
|
|
60
|
+
index={index}
|
|
61
|
+
activeIndex={activeIndex}
|
|
62
|
+
totalItems={items.length}
|
|
63
|
+
/>
|
|
64
|
+
))}
|
|
65
|
+
</View>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type AnimatedTextItemProps = {
|
|
70
|
+
item: TextItem;
|
|
71
|
+
index: number;
|
|
72
|
+
activeIndex: SharedValue<number>;
|
|
73
|
+
totalItems: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const AnimatedTextItem = ({
|
|
77
|
+
item,
|
|
78
|
+
index,
|
|
79
|
+
activeIndex,
|
|
80
|
+
totalItems,
|
|
81
|
+
}: AnimatedTextItemProps) => {
|
|
82
|
+
const { theme } = useTheme();
|
|
83
|
+
const styles = createStyles(theme);
|
|
84
|
+
|
|
85
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
86
|
+
const isActive = activeIndex.value === index;
|
|
87
|
+
const wasActive = activeIndex.value > index;
|
|
88
|
+
const willBeActive = activeIndex.value < index;
|
|
89
|
+
|
|
90
|
+
// Opacity animation
|
|
91
|
+
const opacity = withTiming(
|
|
92
|
+
isActive ? 1 : wasActive ? 0.6 : willBeActive ? 0.4 : 0.4,
|
|
93
|
+
{
|
|
94
|
+
duration: 400,
|
|
95
|
+
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Color interpolation
|
|
100
|
+
const color = interpolateColor(
|
|
101
|
+
isActive ? 1 : 0,
|
|
102
|
+
[0, 1],
|
|
103
|
+
[theme.colors.text.disable, theme.colors.text.primary]
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Subtle translateY for active text
|
|
107
|
+
const translateY = withTiming(isActive ? 0 : 2, {
|
|
108
|
+
duration: 300,
|
|
109
|
+
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Pulse effect for the last active item
|
|
113
|
+
const scale =
|
|
114
|
+
isActive && index === totalItems - 1
|
|
115
|
+
? withSequence(
|
|
116
|
+
withTiming(1, { duration: 1000 }),
|
|
117
|
+
withTiming(1.05, { duration: 1000 }),
|
|
118
|
+
withTiming(1, { duration: 1000 })
|
|
119
|
+
)
|
|
120
|
+
: 1;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
opacity,
|
|
124
|
+
color,
|
|
125
|
+
transform: [{ translateY }, { scale }],
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Animated.Text style={[styles.text, animatedStyle]}>
|
|
131
|
+
{item.label}
|
|
132
|
+
</Animated.Text>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const createStyles = (theme: Theme) =>
|
|
137
|
+
StyleSheet.create({
|
|
138
|
+
container: {
|
|
139
|
+
gap: 8,
|
|
140
|
+
alignItems: "center",
|
|
141
|
+
width: "100%",
|
|
142
|
+
},
|
|
143
|
+
text: {
|
|
144
|
+
fontFamily: theme.typography.textStyles.heading3.fontFamily,
|
|
145
|
+
fontSize: theme.typography.textStyles.heading3.fontSize,
|
|
146
|
+
fontWeight: theme.typography.fontWeight.medium,
|
|
147
|
+
textAlign: "center",
|
|
148
|
+
lineHeight:
|
|
149
|
+
theme.typography.textStyles.heading3.fontSize * theme.typography.lineHeight.normal,
|
|
150
|
+
letterSpacing: 0.3,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import React, { Component, ReactNode } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet, ScrollView } from 'react-native';
|
|
3
|
+
import { ZodError } from 'zod';
|
|
4
|
+
|
|
5
|
+
interface ErrorBoundaryProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
stepType?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ErrorBoundaryState {
|
|
11
|
+
hasError: boolean;
|
|
12
|
+
error: Error | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
16
|
+
constructor(props: ErrorBoundaryProps) {
|
|
17
|
+
super(props);
|
|
18
|
+
this.state = { hasError: false, error: null };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
22
|
+
return { hasError: true, error };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
26
|
+
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
formatZodError(error: ZodError<any>): string {
|
|
30
|
+
try {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
return error.errors
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
.map((err) => {
|
|
35
|
+
const path = err.path.join(' > ');
|
|
36
|
+
return `• ${path || 'root'}: ${err.message}`;
|
|
37
|
+
})
|
|
38
|
+
.join('\n');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error formatting Zod error:', error);
|
|
41
|
+
return 'An error occurred while formatting the Zod error';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
render() {
|
|
46
|
+
if (this.state.hasError && this.state.error) {
|
|
47
|
+
const isZodError = this.state.error instanceof ZodError;
|
|
48
|
+
const { stepType } = this.props;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<View style={styles.container}>
|
|
52
|
+
<View style={styles.content}>
|
|
53
|
+
<Text style={styles.emoji}>⚠️</Text>
|
|
54
|
+
<Text style={styles.title}>
|
|
55
|
+
{isZodError ? 'Invalid Step Payload' : 'Something went wrong'}
|
|
56
|
+
</Text>
|
|
57
|
+
|
|
58
|
+
{stepType && (
|
|
59
|
+
<Text style={styles.stepType}>Step Type: {stepType}</Text>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<ScrollView style={styles.errorScroll} contentContainerStyle={styles.errorScrollContent}>
|
|
63
|
+
{isZodError ? (
|
|
64
|
+
<View style={styles.errorSection}>
|
|
65
|
+
<Text style={styles.errorLabel}>Validation Errors:</Text>
|
|
66
|
+
<Text style={styles.errorMessage}>
|
|
67
|
+
{this.formatZodError(this.state.error as ZodError<any>)}
|
|
68
|
+
</Text>
|
|
69
|
+
</View>
|
|
70
|
+
) : (
|
|
71
|
+
<View style={styles.errorSection}>
|
|
72
|
+
<Text style={styles.errorLabel}>Error Message:</Text>
|
|
73
|
+
<Text style={styles.errorMessage}>
|
|
74
|
+
{this.state.error.message}
|
|
75
|
+
</Text>
|
|
76
|
+
{this.state.error.stack && (
|
|
77
|
+
<>
|
|
78
|
+
<Text style={styles.errorLabel}>Stack Trace:</Text>
|
|
79
|
+
<Text style={styles.errorStack}>
|
|
80
|
+
{this.state.error.stack}
|
|
81
|
+
</Text>
|
|
82
|
+
</>
|
|
83
|
+
)}
|
|
84
|
+
</View>
|
|
85
|
+
)}
|
|
86
|
+
</ScrollView>
|
|
87
|
+
|
|
88
|
+
{isZodError && (
|
|
89
|
+
<View style={styles.hint}>
|
|
90
|
+
<Text style={styles.hintText}>
|
|
91
|
+
💡 Check the step payload structure and ensure all required fields match the schema.
|
|
92
|
+
</Text>
|
|
93
|
+
</View>
|
|
94
|
+
)}
|
|
95
|
+
</View>
|
|
96
|
+
</View>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return this.props.children;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const styles = StyleSheet.create({
|
|
105
|
+
container: {
|
|
106
|
+
flex: 1,
|
|
107
|
+
backgroundColor: '#fff',
|
|
108
|
+
justifyContent: 'center',
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
padding: 20,
|
|
111
|
+
},
|
|
112
|
+
content: {
|
|
113
|
+
maxWidth: 600,
|
|
114
|
+
width: '100%',
|
|
115
|
+
alignItems: 'center',
|
|
116
|
+
},
|
|
117
|
+
emoji: {
|
|
118
|
+
fontSize: 48,
|
|
119
|
+
marginBottom: 16,
|
|
120
|
+
},
|
|
121
|
+
title: {
|
|
122
|
+
fontSize: 24,
|
|
123
|
+
fontWeight: '600',
|
|
124
|
+
color: '#dc2626',
|
|
125
|
+
marginBottom: 8,
|
|
126
|
+
textAlign: 'center',
|
|
127
|
+
},
|
|
128
|
+
stepType: {
|
|
129
|
+
fontSize: 16,
|
|
130
|
+
fontWeight: '500',
|
|
131
|
+
color: '#6b7280',
|
|
132
|
+
marginBottom: 24,
|
|
133
|
+
textAlign: 'center',
|
|
134
|
+
},
|
|
135
|
+
errorScroll: {
|
|
136
|
+
maxHeight: 400,
|
|
137
|
+
width: '100%',
|
|
138
|
+
},
|
|
139
|
+
errorScrollContent: {
|
|
140
|
+
paddingVertical: 16,
|
|
141
|
+
},
|
|
142
|
+
errorSection: {
|
|
143
|
+
backgroundColor: '#fef2f2',
|
|
144
|
+
borderRadius: 12,
|
|
145
|
+
padding: 16,
|
|
146
|
+
borderLeftWidth: 4,
|
|
147
|
+
borderLeftColor: '#dc2626',
|
|
148
|
+
},
|
|
149
|
+
errorLabel: {
|
|
150
|
+
fontSize: 14,
|
|
151
|
+
fontWeight: '600',
|
|
152
|
+
color: '#991b1b',
|
|
153
|
+
marginBottom: 8,
|
|
154
|
+
},
|
|
155
|
+
errorMessage: {
|
|
156
|
+
fontSize: 14,
|
|
157
|
+
color: '#7f1d1d',
|
|
158
|
+
fontFamily: 'Courier',
|
|
159
|
+
lineHeight: 20,
|
|
160
|
+
},
|
|
161
|
+
errorStack: {
|
|
162
|
+
fontSize: 12,
|
|
163
|
+
color: '#991b1b',
|
|
164
|
+
fontFamily: 'Courier',
|
|
165
|
+
lineHeight: 16,
|
|
166
|
+
marginTop: 8,
|
|
167
|
+
},
|
|
168
|
+
hint: {
|
|
169
|
+
marginTop: 24,
|
|
170
|
+
backgroundColor: '#eff6ff',
|
|
171
|
+
borderRadius: 8,
|
|
172
|
+
padding: 12,
|
|
173
|
+
borderLeftWidth: 3,
|
|
174
|
+
borderLeftColor: '#3b82f6',
|
|
175
|
+
},
|
|
176
|
+
hintText: {
|
|
177
|
+
fontSize: 14,
|
|
178
|
+
color: '#1e40af',
|
|
179
|
+
lineHeight: 20,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Error Boundary
|
|
2
|
+
|
|
3
|
+
The Error Boundary is a Higher Order Component (HOC) that wraps all renderers to catch and display errors gracefully, especially Zod validation errors.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Error Catching**: Catches all errors thrown by renderers
|
|
8
|
+
- **Zod Error Formatting**: Specially formats Zod validation errors to show which fields are invalid
|
|
9
|
+
- **Developer-Friendly Display**: Shows error messages, stack traces, and validation details
|
|
10
|
+
- **Type-Specific Context**: Displays which step type caused the error
|
|
11
|
+
|
|
12
|
+
## How It Works
|
|
13
|
+
|
|
14
|
+
All renderers are automatically wrapped with the `withErrorBoundary` HOC:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { QuestionRenderer as QuestionRendererBase } from './Renderer';
|
|
18
|
+
import { withErrorBoundary } from '../../ErrorBoundary';
|
|
19
|
+
|
|
20
|
+
export const QuestionRenderer = withErrorBoundary(QuestionRendererBase, 'Question');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
When a Zod validation error occurs (e.g., missing required fields, wrong types), the error boundary will display:
|
|
24
|
+
|
|
25
|
+
1. Step Type (e.g., "Question", "Ratings")
|
|
26
|
+
2. Validation Errors (formatted list of field paths and messages)
|
|
27
|
+
3. Helpful hints for fixing the payload
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
If you pass an invalid payload:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const invalidStep = {
|
|
35
|
+
id: 'test',
|
|
36
|
+
type: 'Question',
|
|
37
|
+
payload: {
|
|
38
|
+
title: 'Test',
|
|
39
|
+
// Missing required 'answers' field
|
|
40
|
+
// Missing required 'multipleAnswer' field
|
|
41
|
+
}
|
|
42
|
+
} as any;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The error boundary will show:
|
|
46
|
+
```
|
|
47
|
+
⚠️ Invalid Step Payload
|
|
48
|
+
Step Type: Question
|
|
49
|
+
|
|
50
|
+
Validation Errors:
|
|
51
|
+
• payload > answers: Required
|
|
52
|
+
• payload > multipleAnswer: Required
|
|
53
|
+
|
|
54
|
+
💡 Check the step payload structure and ensure all required fields match the schema.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Manual Usage
|
|
58
|
+
|
|
59
|
+
You can also use the ErrorBoundary component directly:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { ErrorBoundary } from '@rocapine/react-native-onboarding-studio';
|
|
63
|
+
|
|
64
|
+
<ErrorBoundary stepType="MyCustomComponent">
|
|
65
|
+
<MyCustomComponent />
|
|
66
|
+
</ErrorBoundary>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Testing
|
|
70
|
+
|
|
71
|
+
An error test example is available at `/example/error-test` in the example app to see the error boundary in action.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
3
|
+
|
|
4
|
+
export function withErrorBoundary<P extends object>(
|
|
5
|
+
Component: React.ComponentType<P>,
|
|
6
|
+
stepType?: string
|
|
7
|
+
) {
|
|
8
|
+
const WrappedComponent = (props: P) => {
|
|
9
|
+
return (
|
|
10
|
+
<ErrorBoundary stepType={stepType}>
|
|
11
|
+
<Component {...props} />
|
|
12
|
+
</ErrorBoundary>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name || 'Component'})`;
|
|
17
|
+
|
|
18
|
+
return WrappedComponent;
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { OnboardingStepType } from "./types";
|
|
2
|
+
import { RatingsRenderer, PickerRenderer, CommitmentRenderer, CarouselRenderer, LoaderRenderer, MediaContentRenderer, QuestionRenderer, QuestionAnswerButtonProps, QuestionAnswersListProps } from "./Pages";
|
|
3
|
+
import { View, Text, Button } from 'react-native';
|
|
4
|
+
import { useTheme } from "./Theme/useTheme";
|
|
5
|
+
import { Theme } from "./Theme";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface OnboardingPageProps {
|
|
9
|
+
step: OnboardingStepType;
|
|
10
|
+
onContinue: (args?: any) => void;
|
|
11
|
+
isSandbox?: boolean;
|
|
12
|
+
theme?: Theme;
|
|
13
|
+
customComponents?: {
|
|
14
|
+
QuestionAnswerButton?: React.ComponentType<QuestionAnswerButtonProps>;
|
|
15
|
+
QuestionAnswersList?: React.ComponentType<QuestionAnswersListProps>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const OnboardingPage = ({ step, onContinue, isSandbox }: OnboardingPageProps) => {
|
|
20
|
+
const { theme } = useTheme();
|
|
21
|
+
|
|
22
|
+
switch (step.type) {
|
|
23
|
+
case 'Ratings':
|
|
24
|
+
return <RatingsRenderer step={step} onContinue={onContinue} theme={theme} />;
|
|
25
|
+
case 'Picker':
|
|
26
|
+
return <PickerRenderer step={step} onContinue={onContinue} theme={theme} />;
|
|
27
|
+
case 'Commitment':
|
|
28
|
+
return <CommitmentRenderer step={step} onContinue={onContinue} theme={theme} />;
|
|
29
|
+
case 'Carousel':
|
|
30
|
+
return <CarouselRenderer step={step} onContinue={onContinue} theme={theme} />;
|
|
31
|
+
case 'MediaContent':
|
|
32
|
+
return <MediaContentRenderer step={step} onContinue={onContinue} theme={theme} />;
|
|
33
|
+
case 'Loader':
|
|
34
|
+
return <LoaderRenderer step={step} onContinue={onContinue} theme={theme} />;
|
|
35
|
+
case 'Question':
|
|
36
|
+
return <QuestionRenderer step={step} onContinue={onContinue} theme={theme} />;
|
|
37
|
+
default:
|
|
38
|
+
if (isSandbox) {
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
const stepType = step.type;
|
|
41
|
+
return <View>
|
|
42
|
+
<Text>Screen {stepType} not implemented</Text>
|
|
43
|
+
<Button title="Continue" onPress={onContinue} />
|
|
44
|
+
</View>
|
|
45
|
+
} else {
|
|
46
|
+
onContinue("onboarding_screen_not_implemented");
|
|
47
|
+
return <View>
|
|
48
|
+
<Text>You are almost done</Text>
|
|
49
|
+
<Button title="Continue" onPress={onContinue} />
|
|
50
|
+
</View>
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|