@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.
Files changed (275) hide show
  1. package/dist/UI/Components/CircularProgress.d.ts +8 -0
  2. package/dist/UI/Components/CircularProgress.d.ts.map +1 -0
  3. package/dist/UI/Components/CircularProgress.js +104 -0
  4. package/dist/UI/Components/CircularProgress.js.map +1 -0
  5. package/dist/UI/Components/ProgressBar.d.ts +12 -0
  6. package/dist/UI/Components/ProgressBar.d.ts.map +1 -0
  7. package/dist/UI/Components/ProgressBar.js +121 -0
  8. package/dist/UI/Components/ProgressBar.js.map +1 -0
  9. package/dist/UI/Components/StaggeredTextList.d.ts +11 -0
  10. package/dist/UI/Components/StaggeredTextList.d.ts.map +1 -0
  11. package/dist/UI/Components/StaggeredTextList.js +111 -0
  12. package/dist/UI/Components/StaggeredTextList.js.map +1 -0
  13. package/dist/UI/Components/index.d.ts +4 -0
  14. package/dist/UI/Components/index.d.ts.map +1 -0
  15. package/dist/UI/Components/index.js +20 -0
  16. package/dist/UI/Components/index.js.map +1 -0
  17. package/dist/UI/ErrorBoundary/ErrorBoundary.d.ts +19 -0
  18. package/dist/UI/ErrorBoundary/ErrorBoundary.d.ts.map +1 -0
  19. package/dist/UI/ErrorBoundary/ErrorBoundary.js +123 -0
  20. package/dist/UI/ErrorBoundary/ErrorBoundary.js.map +1 -0
  21. package/dist/UI/ErrorBoundary/index.d.ts +3 -0
  22. package/dist/UI/ErrorBoundary/index.d.ts.map +1 -0
  23. package/dist/UI/ErrorBoundary/index.js +8 -0
  24. package/dist/UI/ErrorBoundary/index.js.map +1 -0
  25. package/dist/UI/ErrorBoundary/withErrorBoundary.d.ts +6 -0
  26. package/dist/UI/ErrorBoundary/withErrorBoundary.d.ts.map +1 -0
  27. package/dist/UI/ErrorBoundary/withErrorBoundary.js +13 -0
  28. package/dist/UI/ErrorBoundary/withErrorBoundary.js.map +1 -0
  29. package/dist/UI/OnboardingPage.d.ts +16 -0
  30. package/dist/UI/OnboardingPage.d.ts.map +1 -0
  31. package/dist/UI/OnboardingPage.js +38 -0
  32. package/dist/UI/OnboardingPage.js.map +1 -0
  33. package/dist/UI/Pages/Carousel/Renderer.d.ts +13 -0
  34. package/dist/UI/Pages/Carousel/Renderer.d.ts.map +1 -0
  35. package/dist/UI/Pages/Carousel/Renderer.js +121 -0
  36. package/dist/UI/Pages/Carousel/Renderer.js.map +1 -0
  37. package/dist/UI/Pages/Carousel/index.d.ts +3 -0
  38. package/dist/UI/Pages/Carousel/index.d.ts.map +1 -0
  39. package/dist/UI/Pages/Carousel/index.js +19 -0
  40. package/dist/UI/Pages/Carousel/index.js.map +1 -0
  41. package/dist/UI/Pages/Carousel/types.d.ts +32 -0
  42. package/dist/UI/Pages/Carousel/types.d.ts.map +1 -0
  43. package/dist/UI/Pages/Carousel/types.js +24 -0
  44. package/dist/UI/Pages/Carousel/types.js.map +1 -0
  45. package/dist/UI/Pages/Commitment/Renderer.d.ts +13 -0
  46. package/dist/UI/Pages/Commitment/Renderer.d.ts.map +1 -0
  47. package/dist/UI/Pages/Commitment/Renderer.js +173 -0
  48. package/dist/UI/Pages/Commitment/Renderer.js.map +1 -0
  49. package/dist/UI/Pages/Commitment/index.d.ts +3 -0
  50. package/dist/UI/Pages/Commitment/index.d.ts.map +1 -0
  51. package/dist/UI/Pages/Commitment/index.js +19 -0
  52. package/dist/UI/Pages/Commitment/index.js.map +1 -0
  53. package/dist/UI/Pages/Commitment/types.d.ts +41 -0
  54. package/dist/UI/Pages/Commitment/types.d.ts.map +1 -0
  55. package/dist/UI/Pages/Commitment/types.js +27 -0
  56. package/dist/UI/Pages/Commitment/types.js.map +1 -0
  57. package/dist/UI/Pages/Loader/Renderer.d.ts +10 -0
  58. package/dist/UI/Pages/Loader/Renderer.d.ts.map +1 -0
  59. package/dist/UI/Pages/Loader/Renderer.js +215 -0
  60. package/dist/UI/Pages/Loader/Renderer.js.map +1 -0
  61. package/dist/UI/Pages/Loader/index.d.ts +3 -0
  62. package/dist/UI/Pages/Loader/index.d.ts.map +1 -0
  63. package/dist/UI/Pages/Loader/index.js +19 -0
  64. package/dist/UI/Pages/Loader/index.js.map +1 -0
  65. package/dist/UI/Pages/Loader/types.d.ts +57 -0
  66. package/dist/UI/Pages/Loader/types.d.ts.map +1 -0
  67. package/dist/UI/Pages/Loader/types.js +30 -0
  68. package/dist/UI/Pages/Loader/types.js.map +1 -0
  69. package/dist/UI/Pages/MediaContent/Renderer.d.ts +13 -0
  70. package/dist/UI/Pages/MediaContent/Renderer.d.ts.map +1 -0
  71. package/dist/UI/Pages/MediaContent/Renderer.js +76 -0
  72. package/dist/UI/Pages/MediaContent/Renderer.js.map +1 -0
  73. package/dist/UI/Pages/MediaContent/index.d.ts +3 -0
  74. package/dist/UI/Pages/MediaContent/index.d.ts.map +1 -0
  75. package/dist/UI/Pages/MediaContent/index.js +19 -0
  76. package/dist/UI/Pages/MediaContent/index.js.map +1 -0
  77. package/dist/UI/Pages/MediaContent/types.d.ts +44 -0
  78. package/dist/UI/Pages/MediaContent/types.d.ts.map +1 -0
  79. package/dist/UI/Pages/MediaContent/types.js +22 -0
  80. package/dist/UI/Pages/MediaContent/types.js.map +1 -0
  81. package/dist/UI/Pages/Picker/Renderer.d.ts +13 -0
  82. package/dist/UI/Pages/Picker/Renderer.d.ts.map +1 -0
  83. package/dist/UI/Pages/Picker/Renderer.js +268 -0
  84. package/dist/UI/Pages/Picker/Renderer.js.map +1 -0
  85. package/dist/UI/Pages/Picker/index.d.ts +3 -0
  86. package/dist/UI/Pages/Picker/index.d.ts.map +1 -0
  87. package/dist/UI/Pages/Picker/index.js +19 -0
  88. package/dist/UI/Pages/Picker/index.js.map +1 -0
  89. package/dist/UI/Pages/Picker/types.d.ts +49 -0
  90. package/dist/UI/Pages/Picker/types.d.ts.map +1 -0
  91. package/dist/UI/Pages/Picker/types.js +30 -0
  92. package/dist/UI/Pages/Picker/types.js.map +1 -0
  93. package/dist/UI/Pages/Question/Renderer.d.ts +18 -0
  94. package/dist/UI/Pages/Question/Renderer.d.ts.map +1 -0
  95. package/dist/UI/Pages/Question/Renderer.js +128 -0
  96. package/dist/UI/Pages/Question/Renderer.js.map +1 -0
  97. package/dist/UI/Pages/Question/components.d.ts +57 -0
  98. package/dist/UI/Pages/Question/components.d.ts.map +1 -0
  99. package/dist/UI/Pages/Question/components.js +57 -0
  100. package/dist/UI/Pages/Question/components.js.map +1 -0
  101. package/dist/UI/Pages/Question/index.d.ts +4 -0
  102. package/dist/UI/Pages/Question/index.d.ts.map +1 -0
  103. package/dist/UI/Pages/Question/index.js +20 -0
  104. package/dist/UI/Pages/Question/index.js.map +1 -0
  105. package/dist/UI/Pages/Question/types.d.ts +47 -0
  106. package/dist/UI/Pages/Question/types.d.ts.map +1 -0
  107. package/dist/UI/Pages/Question/types.js +28 -0
  108. package/dist/UI/Pages/Question/types.js.map +1 -0
  109. package/dist/UI/Pages/Ratings/Renderer.d.ts +13 -0
  110. package/dist/UI/Pages/Ratings/Renderer.d.ts.map +1 -0
  111. package/dist/UI/Pages/Ratings/Renderer.js +201 -0
  112. package/dist/UI/Pages/Ratings/Renderer.js.map +1 -0
  113. package/dist/UI/Pages/Ratings/index.d.ts +3 -0
  114. package/dist/UI/Pages/Ratings/index.d.ts.map +1 -0
  115. package/dist/UI/Pages/Ratings/index.js +19 -0
  116. package/dist/UI/Pages/Ratings/index.js.map +1 -0
  117. package/dist/UI/Pages/Ratings/types.d.ts +32 -0
  118. package/dist/UI/Pages/Ratings/types.d.ts.map +1 -0
  119. package/dist/UI/Pages/Ratings/types.js +25 -0
  120. package/dist/UI/Pages/Ratings/types.js.map +1 -0
  121. package/dist/UI/Pages/index.d.ts +9 -0
  122. package/dist/UI/Pages/index.d.ts.map +1 -0
  123. package/dist/UI/Pages/index.js +26 -0
  124. package/dist/UI/Pages/index.js.map +1 -0
  125. package/dist/UI/Pages/types.d.ts +19 -0
  126. package/dist/UI/Pages/types.d.ts.map +1 -0
  127. package/dist/UI/Pages/types.js +25 -0
  128. package/dist/UI/Pages/types.js.map +1 -0
  129. package/dist/UI/Provider/OnboardingProgressProvider.d.ts +18 -0
  130. package/dist/UI/Provider/OnboardingProgressProvider.d.ts.map +1 -0
  131. package/dist/UI/Provider/OnboardingProgressProvider.js +23 -0
  132. package/dist/UI/Provider/OnboardingProgressProvider.js.map +1 -0
  133. package/dist/UI/Provider/index.d.ts +2 -0
  134. package/dist/UI/Provider/index.d.ts.map +1 -0
  135. package/dist/UI/Provider/index.js +7 -0
  136. package/dist/UI/Provider/index.js.map +1 -0
  137. package/dist/UI/Templates/OnboardingTemplate.d.ts +15 -0
  138. package/dist/UI/Templates/OnboardingTemplate.d.ts.map +1 -0
  139. package/dist/UI/Templates/OnboardingTemplate.js +48 -0
  140. package/dist/UI/Templates/OnboardingTemplate.js.map +1 -0
  141. package/dist/UI/Templates/index.d.ts +2 -0
  142. package/dist/UI/Templates/index.d.ts.map +1 -0
  143. package/dist/UI/Templates/index.js +6 -0
  144. package/dist/UI/Templates/index.js.map +1 -0
  145. package/dist/UI/Theme/ThemeProvider.d.ts +27 -0
  146. package/dist/UI/Theme/ThemeProvider.d.ts.map +1 -0
  147. package/dist/UI/Theme/ThemeProvider.js +49 -0
  148. package/dist/UI/Theme/ThemeProvider.js.map +1 -0
  149. package/dist/UI/Theme/defaultTheme.d.ts +7 -0
  150. package/dist/UI/Theme/defaultTheme.d.ts.map +1 -0
  151. package/dist/UI/Theme/defaultTheme.js +14 -0
  152. package/dist/UI/Theme/defaultTheme.js.map +1 -0
  153. package/dist/UI/Theme/helpers.d.ts +12 -0
  154. package/dist/UI/Theme/helpers.d.ts.map +1 -0
  155. package/dist/UI/Theme/helpers.js +21 -0
  156. package/dist/UI/Theme/helpers.js.map +1 -0
  157. package/dist/UI/Theme/index.d.ts +8 -0
  158. package/dist/UI/Theme/index.d.ts.map +1 -0
  159. package/dist/UI/Theme/index.js +24 -0
  160. package/dist/UI/Theme/index.js.map +1 -0
  161. package/dist/UI/Theme/token.d.ts +107 -0
  162. package/dist/UI/Theme/token.d.ts.map +1 -0
  163. package/dist/UI/Theme/token.js +108 -0
  164. package/dist/UI/Theme/token.js.map +1 -0
  165. package/dist/UI/Theme/tokens/darkTokens.d.ts +24 -0
  166. package/dist/UI/Theme/tokens/darkTokens.d.ts.map +1 -0
  167. package/dist/UI/Theme/tokens/darkTokens.js +27 -0
  168. package/dist/UI/Theme/tokens/darkTokens.js.map +1 -0
  169. package/dist/UI/Theme/tokens/index.d.ts +4 -0
  170. package/dist/UI/Theme/tokens/index.d.ts.map +1 -0
  171. package/dist/UI/Theme/tokens/index.js +20 -0
  172. package/dist/UI/Theme/tokens/index.js.map +1 -0
  173. package/dist/UI/Theme/tokens/lightTokens.d.ts +24 -0
  174. package/dist/UI/Theme/tokens/lightTokens.d.ts.map +1 -0
  175. package/dist/UI/Theme/tokens/lightTokens.js +27 -0
  176. package/dist/UI/Theme/tokens/lightTokens.js.map +1 -0
  177. package/dist/UI/Theme/tokens/typography.d.ts +65 -0
  178. package/dist/UI/Theme/tokens/typography.d.ts.map +1 -0
  179. package/dist/UI/Theme/tokens/typography.js +68 -0
  180. package/dist/UI/Theme/tokens/typography.js.map +1 -0
  181. package/dist/UI/Theme/types.d.ts +65 -0
  182. package/dist/UI/Theme/types.d.ts.map +1 -0
  183. package/dist/UI/Theme/types.js +3 -0
  184. package/dist/UI/Theme/types.js.map +1 -0
  185. package/dist/UI/Theme/useTheme.d.ts +7 -0
  186. package/dist/UI/Theme/useTheme.d.ts.map +1 -0
  187. package/dist/UI/Theme/useTheme.js +23 -0
  188. package/dist/UI/Theme/useTheme.js.map +1 -0
  189. package/dist/UI/Theme/utils.d.ts +12 -0
  190. package/dist/UI/Theme/utils.d.ts.map +1 -0
  191. package/dist/UI/Theme/utils.js +55 -0
  192. package/dist/UI/Theme/utils.js.map +1 -0
  193. package/dist/UI/index.d.ts +8 -0
  194. package/dist/UI/index.d.ts.map +1 -0
  195. package/dist/UI/index.js +24 -0
  196. package/dist/UI/index.js.map +1 -0
  197. package/dist/UI/types.d.ts +23 -0
  198. package/dist/UI/types.d.ts.map +1 -0
  199. package/dist/UI/types.js +3 -0
  200. package/dist/UI/types.js.map +1 -0
  201. package/dist/assets/laurel-left.png +0 -0
  202. package/dist/assets/laurel-right.png +0 -0
  203. package/dist/assets/star-filled.png +0 -0
  204. package/dist/index.d.ts +9 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +33 -0
  207. package/dist/index.js.map +1 -0
  208. package/dist/provider/CustomComponentsContext.d.ts +57 -0
  209. package/dist/provider/CustomComponentsContext.d.ts.map +1 -0
  210. package/dist/provider/CustomComponentsContext.js +19 -0
  211. package/dist/provider/CustomComponentsContext.js.map +1 -0
  212. package/dist/provider/OnboardingUIProvider.d.ts +44 -0
  213. package/dist/provider/OnboardingUIProvider.d.ts.map +1 -0
  214. package/dist/provider/OnboardingUIProvider.js +33 -0
  215. package/dist/provider/OnboardingUIProvider.js.map +1 -0
  216. package/dist/provider/OnboardingUIProvider.old.d.ts +60 -0
  217. package/dist/provider/OnboardingUIProvider.old.d.ts.map +1 -0
  218. package/dist/provider/OnboardingUIProvider.old.js +53 -0
  219. package/dist/provider/OnboardingUIProvider.old.js.map +1 -0
  220. package/package.json +77 -0
  221. package/src/UI/Components/CircularProgress.tsx +146 -0
  222. package/src/UI/Components/ProgressBar.tsx +143 -0
  223. package/src/UI/Components/StaggeredTextList.tsx +152 -0
  224. package/src/UI/Components/index.ts +3 -0
  225. package/src/UI/ErrorBoundary/ErrorBoundary.tsx +181 -0
  226. package/src/UI/ErrorBoundary/README.md +71 -0
  227. package/src/UI/ErrorBoundary/index.ts +2 -0
  228. package/src/UI/ErrorBoundary/withErrorBoundary.tsx +19 -0
  229. package/src/UI/OnboardingPage.tsx +53 -0
  230. package/src/UI/Pages/Carousel/Renderer.tsx +210 -0
  231. package/src/UI/Pages/Carousel/index.ts +2 -0
  232. package/src/UI/Pages/Carousel/types.ts +26 -0
  233. package/src/UI/Pages/Commitment/Renderer.tsx +312 -0
  234. package/src/UI/Pages/Commitment/index.ts +2 -0
  235. package/src/UI/Pages/Commitment/types.ts +28 -0
  236. package/src/UI/Pages/Loader/Renderer.tsx +417 -0
  237. package/src/UI/Pages/Loader/index.ts +2 -0
  238. package/src/UI/Pages/Loader/types.ts +32 -0
  239. package/src/UI/Pages/MediaContent/Renderer.tsx +130 -0
  240. package/src/UI/Pages/MediaContent/index.ts +2 -0
  241. package/src/UI/Pages/MediaContent/types.ts +26 -0
  242. package/src/UI/Pages/Picker/Renderer.tsx +618 -0
  243. package/src/UI/Pages/Picker/index.ts +2 -0
  244. package/src/UI/Pages/Picker/types.ts +34 -0
  245. package/src/UI/Pages/Question/Renderer.tsx +208 -0
  246. package/src/UI/Pages/Question/components.tsx +130 -0
  247. package/src/UI/Pages/Question/index.ts +3 -0
  248. package/src/UI/Pages/Question/types.ts +29 -0
  249. package/src/UI/Pages/Ratings/Renderer.tsx +282 -0
  250. package/src/UI/Pages/Ratings/index.ts +2 -0
  251. package/src/UI/Pages/Ratings/types.ts +22 -0
  252. package/src/UI/Pages/index.ts +10 -0
  253. package/src/UI/Pages/types.ts +25 -0
  254. package/src/UI/Provider/OnboardingProgressProvider.tsx +40 -0
  255. package/src/UI/Provider/index.ts +1 -0
  256. package/src/UI/Templates/OnboardingTemplate.tsx +86 -0
  257. package/src/UI/Templates/index.ts +1 -0
  258. package/src/UI/Theme/ThemeProvider.tsx +100 -0
  259. package/src/UI/Theme/defaultTheme.ts +12 -0
  260. package/src/UI/Theme/helpers.ts +24 -0
  261. package/src/UI/Theme/index.ts +7 -0
  262. package/src/UI/Theme/token.ts +106 -0
  263. package/src/UI/Theme/tokens/darkTokens.ts +25 -0
  264. package/src/UI/Theme/tokens/index.ts +3 -0
  265. package/src/UI/Theme/tokens/lightTokens.ts +25 -0
  266. package/src/UI/Theme/tokens/typography.ts +66 -0
  267. package/src/UI/Theme/types.ts +72 -0
  268. package/src/UI/Theme/useTheme.ts +22 -0
  269. package/src/UI/Theme/utils.ts +67 -0
  270. package/src/UI/index.ts +7 -0
  271. package/src/UI/types.ts +41 -0
  272. package/src/assets/laurel-left.png +0 -0
  273. package/src/assets/laurel-right.png +0 -0
  274. package/src/assets/star-filled.png +0 -0
  275. package/src/index.ts +28 -0
@@ -0,0 +1,208 @@
1
+ import { useState } from "react";
2
+ import { QuestionStepTypeSchema, QuestionStepType } from "./types";
3
+ import {
4
+ View,
5
+ Text,
6
+ ScrollView,
7
+ TouchableOpacity,
8
+ StyleSheet,
9
+ } from "react-native";
10
+ import { OnboardingTemplate } from "../../Templates/OnboardingTemplate";
11
+ import { getTextStyle } from "../../Theme/helpers";
12
+ import { Theme } from "../../Theme/types";
13
+ import { defaultTheme } from "../../Theme/defaultTheme";
14
+ import {
15
+ DefaultQuestionAnswerButton,
16
+ DefaultQuestionAnswersList,
17
+ QuestionAnswerButtonProps,
18
+ QuestionAnswersListProps,
19
+ } from "./components";
20
+
21
+ interface QuestionRendererProps {
22
+ step: QuestionStepType;
23
+ onContinue?: (...args: any[]) => void;
24
+ theme?: Theme;
25
+ customComponents?: {
26
+ QuestionAnswerButton?: React.ComponentType<QuestionAnswerButtonProps>;
27
+ QuestionAnswersList?: React.ComponentType<QuestionAnswersListProps>;
28
+ };
29
+ }
30
+
31
+ const QuestionRendererBase = ({ step, onContinue, theme = defaultTheme, customComponents }: QuestionRendererProps) => {
32
+
33
+ // Validate the schema
34
+ const validatedData = QuestionStepTypeSchema.parse(step);
35
+ const { title, subtitle, answers, multipleAnswer } = validatedData.payload;
36
+
37
+ const [selected, setSelected] = useState<Record<string, boolean>>({});
38
+
39
+ const handleContinue = () => {
40
+ if (!onContinue) return;
41
+ const selectedAnswers = Object.keys(selected).filter(
42
+ (key) => selected[key]
43
+ );
44
+ onContinue(multipleAnswer ? selectedAnswers : selectedAnswers[0]);
45
+ };
46
+
47
+ const onAnswerSelected = (answer: string) => {
48
+ if (!onContinue) return;
49
+ if (multipleAnswer) {
50
+ toggleSelected(answer);
51
+ } else {
52
+ onContinue(answer);
53
+ }
54
+ };
55
+
56
+ const toggleSelected = (answer: string) => {
57
+ const isNoneOfTheAbove = answer === "None of the above";
58
+
59
+ setSelected((prev) => {
60
+ if (multipleAnswer) {
61
+ let newSelected: Record<string, boolean>;
62
+
63
+ if (isNoneOfTheAbove) {
64
+ return { [answer]: true };
65
+ }
66
+
67
+ newSelected = { ...prev, [answer]: !prev[answer] };
68
+
69
+ if (newSelected["None of the above"]) {
70
+ newSelected["None of the above"] = false;
71
+ }
72
+
73
+ return newSelected;
74
+ } else {
75
+ return { [answer]: true };
76
+ }
77
+ });
78
+ };
79
+
80
+ const isAnySelected = Object.values(selected).some((value) => value);
81
+
82
+ // Priority: Custom full list > Custom button (via DefaultList) > Default implementation
83
+ const AnswersList =
84
+ customComponents?.QuestionAnswersList || DefaultQuestionAnswersList;
85
+ const AnswerButton =
86
+ customComponents?.QuestionAnswerButton || DefaultQuestionAnswerButton;
87
+
88
+ return (
89
+ <OnboardingTemplate
90
+ step={step}
91
+ onContinue={handleContinue || (() => { })}
92
+ theme={theme}
93
+ button={multipleAnswer && isAnySelected ? { text: "Continue" } : undefined}
94
+ >
95
+ <View style={styles.container}>
96
+ {/* Main Content */}
97
+ <View style={styles.contentContainer}>
98
+ {/* Header */}
99
+ <View style={styles.headerSection}>
100
+ <Text
101
+ style={[
102
+ getTextStyle(theme, "heading1"),
103
+ styles.title,
104
+ { color: theme.colors.text.primary },
105
+ ]}
106
+ >
107
+ {title}
108
+ </Text>
109
+ {Boolean(subtitle?.length) ? (
110
+ <Text
111
+ style={[
112
+ getTextStyle(theme, "body"),
113
+ styles.subtitle,
114
+ { color: theme.colors.text.secondary },
115
+ ]}
116
+ >
117
+ {subtitle}
118
+ </Text>
119
+ ) : null}
120
+ </View>
121
+
122
+ {/* Answers */}
123
+ <ScrollView
124
+ style={styles.scrollView}
125
+ contentContainerStyle={styles.scrollContent}
126
+ showsVerticalScrollIndicator={false}
127
+ >
128
+ <AnswersList
129
+ answers={answers}
130
+ selected={selected}
131
+ onAnswerPress={onAnswerSelected}
132
+ multipleAnswer={multipleAnswer}
133
+ theme={theme}
134
+ />
135
+ </ScrollView>
136
+ </View>
137
+ </View>
138
+ </OnboardingTemplate>
139
+ );
140
+ };
141
+
142
+ const styles = StyleSheet.create({
143
+ container: {
144
+ flex: 1,
145
+ },
146
+ contentContainer: {
147
+ flex: 1,
148
+ paddingHorizontal: 24,
149
+ paddingTop: 16,
150
+ gap: 40,
151
+ },
152
+ headerSection: {
153
+ gap: 8,
154
+ },
155
+ title: {
156
+ textAlign: "center",
157
+ },
158
+ subtitle: {
159
+ textAlign: "center",
160
+ },
161
+ scrollView: {
162
+ flex: 1,
163
+ },
164
+ scrollContent: {
165
+ flexGrow: 1,
166
+ },
167
+ answersContainer: {
168
+ gap: 10,
169
+ },
170
+ bottomSection: {
171
+ paddingHorizontal: 32,
172
+ paddingBottom: 8,
173
+ gap: 24,
174
+ alignItems: "center",
175
+ },
176
+ continueButton: {
177
+ backgroundColor: "#262626",
178
+ borderRadius: 90,
179
+ paddingVertical: 18,
180
+ paddingHorizontal: 24,
181
+ minWidth: 234,
182
+ alignItems: "center",
183
+ },
184
+ continueButtonDisabled: {
185
+ backgroundColor: "#d1d1d6",
186
+ },
187
+ continueButtonText: {
188
+ fontFamily: "System",
189
+ fontSize: 16,
190
+ fontWeight: "500",
191
+ color: "#ffffff",
192
+ textAlign: "center",
193
+ },
194
+ homeIndicator: {
195
+ width: 148,
196
+ height: 5,
197
+ backgroundColor: "#000000",
198
+ borderRadius: 100,
199
+ opacity: 0.3,
200
+ },
201
+ });
202
+
203
+ import { withErrorBoundary } from "../../ErrorBoundary";
204
+
205
+ export const QuestionRenderer = withErrorBoundary(
206
+ QuestionRendererBase,
207
+ "Question"
208
+ );
@@ -0,0 +1,130 @@
1
+ import React from "react";
2
+ import {
3
+ TouchableOpacity,
4
+ Text,
5
+ View,
6
+ StyleSheet,
7
+ ViewStyle,
8
+ } from "react-native";
9
+ import { Theme } from "../../Theme/types";
10
+ import { getTextStyle } from "../../Theme/helpers";
11
+
12
+ /**
13
+ * Props for custom Question answer button component.
14
+ * Individual button in the answers list.
15
+ */
16
+ export interface QuestionAnswerButtonProps {
17
+ /** The answer data (label and value) */
18
+ answer: { label: string; value: string };
19
+ /** Whether this answer is currently selected */
20
+ selected: boolean;
21
+ /** Callback when button is pressed */
22
+ onPress: () => void;
23
+ /** Current theme */
24
+ theme: Theme;
25
+ /** Index in the list (0-based) */
26
+ index: number;
27
+ /** True if this is the first answer */
28
+ isFirst: boolean;
29
+ /** True if this is the last answer */
30
+ isLast: boolean;
31
+ }
32
+
33
+ /**
34
+ * Props for custom Question answers list component.
35
+ * Full control over the entire answers list rendering.
36
+ */
37
+ export interface QuestionAnswersListProps {
38
+ /** All available answers */
39
+ answers: Array<{ label: string; value: string }>;
40
+ /** Record of which answers are selected (value -> boolean) */
41
+ selected: Record<string, boolean>;
42
+ /** Callback when an answer is pressed */
43
+ onAnswerPress: (value: string) => void;
44
+ /** Whether multiple answers can be selected */
45
+ multipleAnswer: boolean;
46
+ /** Current theme */
47
+ theme: Theme;
48
+ }
49
+
50
+ /**
51
+ * Default answer button component.
52
+ * Can be used standalone or wrapped in custom implementations.
53
+ */
54
+ export const DefaultQuestionAnswerButton: React.FC<
55
+ QuestionAnswerButtonProps
56
+ > = ({ answer, selected, onPress, theme }) => (
57
+ <TouchableOpacity
58
+ style={[
59
+ styles.answerButton,
60
+ { backgroundColor: theme.colors.neutral.lowest, borderColor: theme.colors.neutral.lower },
61
+ selected && [
62
+ styles.answerButtonSelected,
63
+ {
64
+ backgroundColor: theme.colors.primary,
65
+ borderColor: theme.colors.primary,
66
+ },
67
+ ],
68
+ ]}
69
+ onPress={onPress}
70
+ activeOpacity={0.7}
71
+ >
72
+ <Text
73
+ style={[
74
+ getTextStyle(theme, "body"),
75
+ styles.answerText,
76
+ { color: theme.colors.text.primary },
77
+ selected && [
78
+ styles.answerTextSelected,
79
+ { color: theme.colors.text.opposite },
80
+ ],
81
+ ]}
82
+ >
83
+ {answer.label}
84
+ </Text>
85
+ </TouchableOpacity>
86
+ );
87
+
88
+ /**
89
+ * Default answers list component.
90
+ * Renders all answers using DefaultQuestionAnswerButton or custom button if provided via context.
91
+ * Note: This component needs to be wrapped with custom components context provider to work properly.
92
+ * It will be imported dynamically in the renderer to avoid circular dependencies.
93
+ */
94
+ export const DefaultQuestionAnswersList: React.FC<
95
+ QuestionAnswersListProps
96
+ > = ({ answers, selected, onAnswerPress, theme }) => {
97
+ return (
98
+ <View style={styles.answersContainer}>
99
+ {answers.map((answer, index) => (
100
+ <DefaultQuestionAnswerButton
101
+ key={answer.value}
102
+ answer={answer}
103
+ selected={selected[answer.value]}
104
+ onPress={() => onAnswerPress(answer.value)}
105
+ theme={theme}
106
+ index={index}
107
+ isFirst={index === 0}
108
+ isLast={index === answers.length - 1}
109
+ />
110
+ ))}
111
+ </View>
112
+ );
113
+ };
114
+
115
+ const styles = StyleSheet.create({
116
+ answerButton: {
117
+ borderRadius: 16,
118
+ paddingVertical: 20,
119
+ paddingHorizontal: 24,
120
+ borderWidth: 1,
121
+ },
122
+ answerButtonSelected: {},
123
+ answerText: {
124
+ textAlign: "center",
125
+ },
126
+ answerTextSelected: {},
127
+ answersContainer: {
128
+ gap: 10,
129
+ },
130
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./Renderer";
2
+ export * from "./types";
3
+ export * from "./components";
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { CustomPayloadSchema, InfoBoxSchema } from "../types";
3
+
4
+ export const AnswerSchema = z.object({
5
+ label: z.string(),
6
+ value: z.string(),
7
+ icon: z.string().nullish(),
8
+ description: z.string().nullish(),
9
+ });
10
+
11
+ export const QuestionStepPayloadSchema = z.object({
12
+ answers: z.array(AnswerSchema),
13
+ title: z.string(),
14
+ subtitle: z.string().nullish(),
15
+ multipleAnswer: z.boolean(),
16
+ infoBox: InfoBoxSchema.nullish(),
17
+ });
18
+
19
+ export const QuestionStepTypeSchema = z.object({
20
+ id: z.string(),
21
+ type: z.literal("Question"),
22
+ name: z.string(),
23
+ displayProgressHeader: z.boolean(),
24
+ payload: QuestionStepPayloadSchema,
25
+ customPayload: CustomPayloadSchema,
26
+ figmaUrl: z.string().nullish(),
27
+ });
28
+
29
+ export type QuestionStepType = z.infer<typeof QuestionStepTypeSchema>;
@@ -0,0 +1,282 @@
1
+ import { Image, ScrollView, StyleSheet, Text, View } from "react-native";
2
+ import Svg, { Path } from "react-native-svg";
3
+ import { OnboardingTemplate } from "../../Templates/OnboardingTemplate";
4
+ import { RatingsStepType, RatingsStepTypeSchema } from "./types";
5
+ import { useState } from "react";
6
+ import { Theme } from "../../Theme/types";
7
+ import { defaultTheme } from "../../Theme/defaultTheme";
8
+ import { getTextStyle } from "../../Theme/helpers";
9
+
10
+ // Lazy load StoreReview - only needed for ratings screens
11
+ let StoreReview: any;
12
+ try {
13
+ StoreReview = require("expo-store-review");
14
+ } catch (e) {
15
+ // StoreReview not installed - will show error when ratings screen is used
16
+ StoreReview = null;
17
+ }
18
+
19
+ interface RatingsRendererProps {
20
+ step: RatingsStepType;
21
+ onContinue?: () => void;
22
+ theme?: Theme;
23
+ }
24
+
25
+ const StarIcon = ({ size, filled }: { size: number; filled: boolean }) => (
26
+ <Svg width={size} height={size} viewBox="0 0 32 32" fill="none">
27
+ <Path
28
+ d="M16 2L20.12 11.76L31 13.24L23.5 20.48L25.24 31.24L16 26.76L6.76 31.24L8.5 20.48L1 13.24L11.88 11.76L16 2Z"
29
+ fill={filled ? "#FED64B" : "none"}
30
+ stroke={filled ? "#FED64B" : "#D1D1D6"}
31
+ strokeWidth="1.5"
32
+ strokeLinecap="round"
33
+ strokeLinejoin="round"
34
+ />
35
+ </Svg>
36
+ );
37
+
38
+ const RatingsRendererBase = ({ step, onContinue, theme = defaultTheme }: RatingsRendererProps) => {
39
+ const [hasOpenedRequestReview, setHasOpenedRequestReview] = useState(false);
40
+
41
+ // Check if StoreReview is available
42
+ if (!StoreReview) {
43
+ throw new Error(
44
+ "Ratings screens require expo-store-review. Install it with: npx expo install expo-store-review"
45
+ );
46
+ }
47
+
48
+ const handlePress = async () => {
49
+ if (!hasOpenedRequestReview) {
50
+ setHasOpenedRequestReview(true);
51
+ if (await StoreReview.hasAction()) {
52
+ // you can call StoreReview.requestReview()
53
+ await StoreReview.requestReview();
54
+ }
55
+ } else {
56
+ onContinue?.();
57
+ }
58
+ };
59
+ // Validate the schema
60
+ const validatedData = RatingsStepTypeSchema.parse(step);
61
+ const { title, subtitle, socialProofs, rateTheAppButtonLabel } =
62
+ validatedData.payload;
63
+
64
+ // Get the first social proof to display (as shown in design)
65
+ const mainReview = socialProofs[0];
66
+ const otherUsersCount = socialProofs.length > 1 ? socialProofs.length - 1 : 0;
67
+
68
+ const renderStars = (numberOfStar: number, size: number = 20) => {
69
+ return (
70
+ <View style={styles.starsContainer}>
71
+ {Array.from({ length: 5 }).map((_, index) => (
72
+ <StarIcon key={index} size={size} filled={index < numberOfStar} />
73
+ ))}
74
+ </View>
75
+ );
76
+ };
77
+
78
+ return (
79
+ <OnboardingTemplate
80
+ step={step}
81
+ onContinue={handlePress}
82
+ theme={theme}
83
+ button={{
84
+ text: !hasOpenedRequestReview
85
+ ? rateTheAppButtonLabel
86
+ : validatedData.continueButtonLabel,
87
+ }}
88
+ >
89
+ <View style={styles.container}>
90
+ {/* Main Content */}
91
+ <ScrollView
92
+ contentContainerStyle={styles.scrollContent}
93
+ showsVerticalScrollIndicator={false}
94
+ >
95
+ {/* Award Section */}
96
+ <View style={styles.awardSection}>
97
+ <View style={styles.awardContainer}>
98
+ <Image
99
+ source={require("../../../assets/laurel-left.png")}
100
+ style={styles.laurelImage}
101
+ resizeMode="contain"
102
+ />
103
+ <View style={styles.awardTextContainer}>
104
+ {renderStars(5, 32)}
105
+ <Text style={[getTextStyle(theme, "heading2"), styles.awardTitle, { color: theme.colors.text.secondary }]}>Users Choice</Text>
106
+ </View>
107
+ <Image
108
+ source={require("../../../assets/laurel-right.png")}
109
+ style={styles.laurelImage}
110
+ resizeMode="contain"
111
+ />
112
+ </View>
113
+ </View>
114
+
115
+ {/* Review Section */}
116
+ <View style={styles.reviewSection}>
117
+ <View style={[styles.reviewCard, { backgroundColor: theme.colors.neutral.lowestest }]}>
118
+ <View style={styles.reviewAuthor}>
119
+ <View style={[styles.avatar, { backgroundColor: theme.colors.neutral.low }]}>
120
+ <Text style={[styles.avatarText, { color: theme.colors.text.opposite }]}>
121
+ {mainReview.authorName.charAt(0).toUpperCase()}
122
+ </Text>
123
+ </View>
124
+ <Text style={[getTextStyle(theme, "label"), styles.authorName, { color: theme.colors.text.secondary }]}>{mainReview.authorName}</Text>
125
+ </View>
126
+
127
+ <Text style={[getTextStyle(theme, "bodyMedium"), styles.reviewContent, { color: theme.colors.text.primary }]}>{mainReview.content}</Text>
128
+
129
+ {renderStars(mainReview.numberOfStar)}
130
+ </View>
131
+
132
+ {/* Other Users Count */}
133
+ {otherUsersCount > 0 && (
134
+ <View style={styles.usersCount}>
135
+ <View style={styles.avatarGroup}>
136
+ {socialProofs.slice(1, 4).map((proof, index) => (
137
+ <View
138
+ key={index}
139
+ style={[
140
+ styles.smallAvatar,
141
+ {
142
+ zIndex: 3 - index,
143
+ marginLeft: index > 0 ? -10 : 0,
144
+ borderColor: theme.colors.neutral.lowestest,
145
+ backgroundColor: theme.colors.neutral.low,
146
+ },
147
+ ]}
148
+ >
149
+ <Text style={[styles.smallAvatarText, { color: theme.colors.text.opposite }]}>
150
+ {proof.authorName.charAt(0).toUpperCase()}
151
+ </Text>
152
+ </View>
153
+ ))}
154
+ </View>
155
+ <Text style={[getTextStyle(theme, "bodyMedium"), styles.usersCountText, { color: theme.colors.text.secondary }]}>
156
+ +{otherUsersCount.toLocaleString()} others
157
+ </Text>
158
+ </View>
159
+ )}
160
+ </View>
161
+
162
+ {/* Title and Subtitle */}
163
+ <View style={styles.textSection}>
164
+ <Text style={[getTextStyle(theme, "heading1"), styles.title, { color: theme.colors.text.primary }]}>{title}</Text>
165
+ <Text style={[getTextStyle(theme, "heading3"), styles.subtitle, { color: theme.colors.text.secondary }]}>{subtitle}</Text>
166
+ </View>
167
+ </ScrollView>
168
+ </View>
169
+ </OnboardingTemplate>
170
+ );
171
+ };
172
+
173
+ const styles = StyleSheet.create({
174
+ container: {
175
+ flex: 1,
176
+ },
177
+ scrollContent: {
178
+ flexGrow: 1,
179
+ paddingBottom: 48,
180
+ paddingTop: 20,
181
+ justifyContent: "space-between",
182
+ },
183
+ awardSection: {
184
+ flex: 1,
185
+ alignItems: "center",
186
+ },
187
+ awardContainer: {
188
+ flexDirection: "row",
189
+ alignItems: "center",
190
+ gap: 8,
191
+ },
192
+ laurelImage: {
193
+ width: 27,
194
+ height: 63,
195
+ },
196
+ awardTextContainer: {
197
+ alignItems: "center",
198
+ gap: 12,
199
+ },
200
+ awardTitle: {
201
+ textAlign: "center",
202
+ },
203
+ starsContainer: {
204
+ flexDirection: "row",
205
+ justifyContent: "center",
206
+ gap: 4,
207
+ },
208
+ reviewSection: {
209
+ paddingHorizontal: 32,
210
+ gap: 24,
211
+ flex: 1,
212
+ justifyContent: "center",
213
+ alignItems: "center",
214
+ },
215
+ reviewCard: {
216
+ borderRadius: 24,
217
+ padding: 20,
218
+ width: "100%",
219
+ maxWidth: 326,
220
+ gap: 16,
221
+ },
222
+ reviewAuthor: {
223
+ flexDirection: "row",
224
+ alignItems: "center",
225
+ gap: 8,
226
+ },
227
+ avatar: {
228
+ width: 32,
229
+ height: 32,
230
+ borderRadius: 16,
231
+ alignItems: "center",
232
+ justifyContent: "center",
233
+ },
234
+ avatarText: {
235
+ fontSize: 14,
236
+ fontWeight: "600",
237
+ },
238
+ authorName: {},
239
+ reviewContent: {
240
+ textAlign: "center",
241
+ },
242
+ usersCount: {
243
+ flexDirection: "row",
244
+ alignItems: "center",
245
+ gap: 8,
246
+ },
247
+ avatarGroup: {
248
+ flexDirection: "row",
249
+ },
250
+ smallAvatar: {
251
+ width: 32,
252
+ height: 32,
253
+ borderRadius: 16,
254
+ alignItems: "center",
255
+ justifyContent: "center",
256
+ borderWidth: 2,
257
+ },
258
+ smallAvatarText: {
259
+ fontSize: 12,
260
+ fontWeight: "600",
261
+ },
262
+ usersCountText: {},
263
+ textSection: {
264
+ paddingHorizontal: 32,
265
+ gap: 16,
266
+ alignItems: "center",
267
+ marginTop: "auto",
268
+ },
269
+ title: {
270
+ textAlign: "center",
271
+ },
272
+ subtitle: {
273
+ textAlign: "center",
274
+ },
275
+ });
276
+
277
+ import { withErrorBoundary } from "../../ErrorBoundary";
278
+
279
+ export const RatingsRenderer = withErrorBoundary(
280
+ RatingsRendererBase,
281
+ "Ratings"
282
+ );
@@ -0,0 +1,2 @@
1
+ export * from "./Renderer";
2
+ export * from "./types";
@@ -0,0 +1,22 @@
1
+ import z from "zod";
2
+ import { CustomPayloadSchema, SocialProofSchema } from "../types";
3
+
4
+ export const RatingsStepPayloadSchema = z.object({
5
+ title: z.string(),
6
+ subtitle: z.string(),
7
+ socialProofs: z.array(SocialProofSchema),
8
+ rateTheAppButtonLabel: z.string().optional().default("Rate the app"),
9
+ });
10
+
11
+ export const RatingsStepTypeSchema = z.object({
12
+ id: z.string(),
13
+ type: z.literal("Ratings"),
14
+ name: z.string(),
15
+ displayProgressHeader: z.boolean(),
16
+ payload: RatingsStepPayloadSchema,
17
+ customPayload: CustomPayloadSchema,
18
+ continueButtonLabel: z.string().optional().default("Continue"),
19
+ figmaUrl: z.string().nullish(),
20
+ });
21
+
22
+ export type RatingsStepType = z.infer<typeof RatingsStepTypeSchema>;
@@ -0,0 +1,10 @@
1
+ export * from "./Carousel";
2
+ export * from "./Commitment";
3
+ export * from "./Loader";
4
+ export * from "./MediaContent";
5
+ export * from "./Picker";
6
+ export * from "./Question";
7
+ export * from "./Ratings";
8
+
9
+ // common types
10
+ export * from "./types";