@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,618 @@
1
+ import { OnboardingTemplate } from "../../Templates/OnboardingTemplate";
2
+ import { PickerStepType, PickerStepTypeSchema, WeightUnit, HeightUnit } from "./types";
3
+ import { View, Text, StyleSheet, TextInput, KeyboardAvoidingView, Platform, TouchableWithoutFeedback, Keyboard } from "react-native";
4
+ import { useState } from "react";
5
+ import { Theme } from "../../Theme/types";
6
+ import { defaultTheme } from "../../Theme/defaultTheme";
7
+ import { getTextStyle } from "../../Theme/helpers";
8
+
9
+ // Lazy load Picker - only needed for picker screens
10
+ let PickerComponent: any;
11
+ try {
12
+ const PickerModule = require("@react-native-picker/picker");
13
+ PickerComponent = PickerModule.Picker;
14
+ } catch (e) {
15
+ // Picker not installed - will show error when picker screen is used
16
+ PickerComponent = null;
17
+ }
18
+
19
+ type ContentProps = {
20
+ step: PickerStepType;
21
+ onContinue: (value?: string | number) => void;
22
+ theme?: Theme;
23
+ };
24
+
25
+ const PickerRendererBase = ({ step, onContinue, theme = defaultTheme }: ContentProps) => {
26
+ const validatedData = PickerStepTypeSchema.parse(step);
27
+ const { title, description, pickerType } = validatedData.payload;
28
+
29
+ // Check if Picker is available
30
+ if (!PickerComponent) {
31
+ throw new Error(
32
+ "Picker screens require @react-native-picker/picker. Install it with: npm install @react-native-picker/picker"
33
+ );
34
+ }
35
+
36
+ // Route to specific picker implementation based on type
37
+ if (pickerType === "weight") {
38
+ return (
39
+ <WeightPicker
40
+ step={validatedData}
41
+ onContinue={onContinue}
42
+ title={title}
43
+ description={description}
44
+ theme={theme}
45
+ />
46
+ );
47
+ }
48
+
49
+ if (pickerType === "height") {
50
+ return (
51
+ <HeightPicker
52
+ step={validatedData}
53
+ onContinue={onContinue}
54
+ title={title}
55
+ description={description}
56
+ theme={theme}
57
+ />
58
+ );
59
+ }
60
+
61
+ if (pickerType === "name") {
62
+ return (
63
+ <NamePicker
64
+ step={validatedData}
65
+ onContinue={onContinue}
66
+ title={title}
67
+ description={description}
68
+ theme={theme}
69
+ />
70
+ );
71
+ }
72
+
73
+ if (pickerType === "date") {
74
+ return (
75
+ <DatePicker
76
+ step={validatedData}
77
+ onContinue={onContinue}
78
+ title={title}
79
+ description={description}
80
+ theme={theme}
81
+ />
82
+ );
83
+ }
84
+
85
+ // Fallback for other picker types (to be implemented)
86
+ return (
87
+ <OnboardingTemplate
88
+ step={step}
89
+ onContinue={() => onContinue()}
90
+ theme={theme}
91
+ button={{ text: validatedData.continueButtonLabel }}
92
+ >
93
+ <View style={styles.container}>
94
+ <Text style={[getTextStyle(theme, "heading1"), styles.title, { color: theme.colors.text.primary }]}>{title}</Text>
95
+ {description && <Text style={[getTextStyle(theme, "body"), styles.description, { color: theme.colors.text.secondary }]}>{description}</Text>}
96
+ <View style={[styles.placeholderContainer, { backgroundColor: theme.colors.neutral.lowest }]}>
97
+ <Text style={[getTextStyle(theme, "body"), styles.placeholderText, { color: theme.colors.text.secondary }]}>
98
+ Picker type "{pickerType}" not yet implemented
99
+ </Text>
100
+ </View>
101
+ </View>
102
+ </OnboardingTemplate>
103
+ );
104
+ };
105
+
106
+ type WeightPickerProps = {
107
+ step: PickerStepType;
108
+ onContinue: (value?: string | number) => void;
109
+ title: string;
110
+ description: string | null | undefined;
111
+ theme: Theme;
112
+ };
113
+
114
+ const WeightPicker = ({
115
+ step,
116
+ onContinue,
117
+ title,
118
+ description,
119
+ theme,
120
+ }: WeightPickerProps) => {
121
+ const [selectedWeight, setSelectedWeight] = useState<number>(70);
122
+ const [unit, setUnit] = useState<WeightUnit>("kg");
123
+
124
+ const generateWeightOptions = (unit: WeightUnit) => {
125
+ const config = {
126
+ lb: { max: 660, step: 1 },
127
+ kg: { max: 300, step: 1 },
128
+ } as const;
129
+
130
+ const { max, step } = config[unit];
131
+ const options: React.ReactNode[] = [];
132
+
133
+ for (let i = max; i >= step; i -= step) {
134
+ options.push(<PickerComponent.Item key={i} label={i.toString()} value={i} />);
135
+ }
136
+
137
+ return options;
138
+ };
139
+
140
+ const handleContinue = () => {
141
+ // Return weight as "value-unit" format, e.g., "70-kg"
142
+ onContinue(`${selectedWeight}-${unit}`);
143
+ };
144
+
145
+ return (
146
+ <OnboardingTemplate
147
+ step={step}
148
+ onContinue={handleContinue}
149
+ theme={theme}
150
+ button={{ text: step.continueButtonLabel }}
151
+ >
152
+ <View style={styles.container}>
153
+ <View style={styles.textContainer}>
154
+ <Text style={[getTextStyle(theme, "heading1"), styles.title, { color: theme.colors.text.primary }]}>{title}</Text>
155
+ {description && (
156
+ <Text style={[getTextStyle(theme, "body"), styles.description, { color: theme.colors.text.secondary }]}>{description}</Text>
157
+ )}
158
+ </View>
159
+
160
+ <View style={styles.pickerContainer}>
161
+ <View style={styles.pickerRow}>
162
+ <PickerComponent
163
+ selectedValue={selectedWeight}
164
+ onValueChange={(itemValue: number) => setSelectedWeight(Number(itemValue))}
165
+ style={styles.weightPicker}
166
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
167
+ >
168
+ {generateWeightOptions(unit)}
169
+ </PickerComponent>
170
+ <PickerComponent
171
+ selectedValue={unit}
172
+ onValueChange={(itemValue: WeightUnit) => {
173
+ if (itemValue === "kg") {
174
+ setSelectedWeight(70);
175
+ } else if (itemValue === "lb") {
176
+ setSelectedWeight(154);
177
+ }
178
+ setUnit(itemValue);
179
+ }}
180
+ style={styles.unitPicker}
181
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
182
+ >
183
+ <PickerComponent.Item label="lb" value="lb" />
184
+ <PickerComponent.Item label="kg" value="kg" />
185
+ </PickerComponent>
186
+ </View>
187
+ </View>
188
+ </View>
189
+ </OnboardingTemplate>
190
+ );
191
+ };
192
+
193
+ type HeightPickerProps = {
194
+ step: PickerStepType;
195
+ onContinue: (value?: string | number) => void;
196
+ title: string;
197
+ description: string | null | undefined;
198
+ theme: Theme;
199
+ };
200
+
201
+ const HeightPicker = ({
202
+ step,
203
+ onContinue,
204
+ title,
205
+ description,
206
+ theme,
207
+ }: HeightPickerProps) => {
208
+ const [unit, setUnit] = useState<HeightUnit>("cm");
209
+
210
+ // For metric (cm)
211
+ const [selectedCm, setSelectedCm] = useState<number>(170);
212
+
213
+ // For imperial (ft + in)
214
+ const [selectedFeet, setSelectedFeet] = useState<number>(5);
215
+ const [selectedInches, setSelectedInches] = useState<number>(7);
216
+
217
+ const generateCmOptions = () => {
218
+ const options: React.ReactNode[] = [];
219
+ for (let i = 250; i >= 100; i--) {
220
+ options.push(<PickerComponent.Item key={i} label={i.toString()} value={i} />);
221
+ }
222
+ return options;
223
+ };
224
+
225
+ const generateFeetOptions = () => {
226
+ const options: React.ReactNode[] = [];
227
+ for (let i = 8; i >= 3; i--) {
228
+ options.push(<PickerComponent.Item key={i} label={i.toString()} value={i} />);
229
+ }
230
+ return options;
231
+ };
232
+
233
+ const generateInchesOptions = () => {
234
+ const options: React.ReactNode[] = [];
235
+ for (let i = 11; i >= 0; i--) {
236
+ options.push(<PickerComponent.Item key={i} label={i.toString()} value={i} />);
237
+ }
238
+ return options;
239
+ };
240
+
241
+ const handleContinue = () => {
242
+ // Return height based on unit
243
+ if (unit === "cm") {
244
+ onContinue(`${selectedCm}-${unit}`);
245
+ } else {
246
+ // Return as "feet-inches-ft" format, e.g., "5-7-ft"
247
+ onContinue(`${selectedFeet}-${selectedInches}-${unit}`);
248
+ }
249
+ };
250
+
251
+ const handleUnitChange = (newUnit: HeightUnit) => {
252
+ if (newUnit === "cm") {
253
+ setSelectedCm(170);
254
+ } else if (newUnit === "ft") {
255
+ setSelectedFeet(5);
256
+ setSelectedInches(7);
257
+ }
258
+ setUnit(newUnit);
259
+ };
260
+
261
+ return (
262
+ <OnboardingTemplate
263
+ step={step}
264
+ onContinue={handleContinue}
265
+ theme={theme}
266
+ button={{ text: step.continueButtonLabel }}
267
+ >
268
+ <View style={styles.container}>
269
+ <View style={styles.textContainer}>
270
+ <Text style={[getTextStyle(theme, "heading1"), styles.title, { color: theme.colors.text.primary }]}>{title}</Text>
271
+ {description && (
272
+ <Text style={[getTextStyle(theme, "body"), styles.description, { color: theme.colors.text.secondary }]}>{description}</Text>
273
+ )}
274
+ </View>
275
+
276
+ <View style={styles.pickerContainer}>
277
+ {unit === "cm" ? (
278
+ <View style={styles.pickerRow}>
279
+ <PickerComponent
280
+ selectedValue={selectedCm}
281
+ onValueChange={(itemValue: number) => setSelectedCm(Number(itemValue))}
282
+ style={styles.weightPicker}
283
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
284
+ >
285
+ {generateCmOptions()}
286
+ </PickerComponent>
287
+ <PickerComponent
288
+ selectedValue={unit}
289
+ onValueChange={(itemValue: HeightUnit) => handleUnitChange(itemValue)}
290
+ style={styles.unitPicker}
291
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
292
+ >
293
+ <PickerComponent.Item label="cm" value="cm" />
294
+ <PickerComponent.Item label="ft" value="ft" />
295
+ </PickerComponent>
296
+ </View>
297
+ ) : (
298
+ <View style={styles.heightImperialContainer}>
299
+ <View style={styles.pickerRow}>
300
+ <PickerComponent
301
+ selectedValue={selectedFeet}
302
+ onValueChange={(itemValue: number) => setSelectedFeet(Number(itemValue))}
303
+ style={styles.heightFeetPicker}
304
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
305
+ >
306
+ {generateFeetOptions()}
307
+ </PickerComponent>
308
+ <PickerComponent
309
+ selectedValue={selectedInches}
310
+ onValueChange={(itemValue: number) => setSelectedInches(Number(itemValue))}
311
+ style={styles.heightInchesPicker}
312
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
313
+ >
314
+ {generateInchesOptions()}
315
+ </PickerComponent>
316
+ <PickerComponent
317
+ selectedValue={unit}
318
+ onValueChange={(itemValue: HeightUnit) => handleUnitChange(itemValue)}
319
+ style={styles.heightUnitPicker}
320
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
321
+ >
322
+ <PickerComponent.Item label="cm" value="cm" />
323
+ <PickerComponent.Item label="ft" value="ft" />
324
+ </PickerComponent>
325
+ </View>
326
+ </View>
327
+ )}
328
+ </View>
329
+ </View>
330
+ </OnboardingTemplate>
331
+ );
332
+ };
333
+
334
+ type NamePickerProps = {
335
+ step: PickerStepType;
336
+ onContinue: (value?: string | number) => void;
337
+ title: string;
338
+ description: string | null | undefined;
339
+ theme: Theme;
340
+ };
341
+
342
+ const NamePicker = ({
343
+ step,
344
+ onContinue,
345
+ title,
346
+ description,
347
+ theme,
348
+ }: NamePickerProps) => {
349
+ const [name, setName] = useState<string>("");
350
+
351
+ const handleContinue = () => {
352
+ if (name.trim()) {
353
+ onContinue(name.trim());
354
+ }
355
+ };
356
+
357
+ const dismissKeyboard = () => {
358
+ Keyboard.dismiss();
359
+ };
360
+
361
+ return (
362
+ <KeyboardAvoidingView
363
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
364
+ style={{ flex: 1 }}
365
+ >
366
+ <TouchableWithoutFeedback onPress={dismissKeyboard}>
367
+ <View style={{ flex: 1 }}>
368
+ <OnboardingTemplate
369
+ step={step}
370
+ onContinue={handleContinue}
371
+ theme={theme}
372
+ button={{
373
+ text: step.continueButtonLabel,
374
+ disabled: !name.trim(),
375
+ }}
376
+ >
377
+ <View style={styles.container}>
378
+ <View style={styles.textContainer}>
379
+ <Text style={[getTextStyle(theme, "heading1"), styles.title, { color: theme.colors.text.primary }]}>
380
+ {title}
381
+ </Text>
382
+ {description && (
383
+ <Text style={[getTextStyle(theme, "body"), styles.description, { color: theme.colors.text.secondary }]}>
384
+ {description}
385
+ </Text>
386
+ )}
387
+ </View>
388
+
389
+ <View style={styles.nameInputContainer}>
390
+ <TextInput
391
+ autoFocus
392
+ value={name}
393
+ onChangeText={setName}
394
+ returnKeyType="done"
395
+ placeholder="Type to write"
396
+ placeholderTextColor={theme.colors.text.disable}
397
+ onSubmitEditing={() => name.trim() && handleContinue()}
398
+ style={[
399
+ styles.nameInput,
400
+ {
401
+ backgroundColor: theme.colors.neutral.lowest,
402
+ color: theme.colors.text.primary,
403
+ },
404
+ ]}
405
+ />
406
+ </View>
407
+ </View>
408
+ </OnboardingTemplate>
409
+ </View>
410
+ </TouchableWithoutFeedback>
411
+ </KeyboardAvoidingView>
412
+ );
413
+ };
414
+
415
+ type DatePickerProps = {
416
+ step: PickerStepType;
417
+ onContinue: (value?: string | number) => void;
418
+ title: string;
419
+ description: string | null | undefined;
420
+ theme: Theme;
421
+ };
422
+
423
+ const DatePicker = ({
424
+ step,
425
+ onContinue,
426
+ title,
427
+ description,
428
+ theme,
429
+ }: DatePickerProps) => {
430
+
431
+ // Get current date as default
432
+ const now = new Date();
433
+ const [selectedMonth, setSelectedMonth] = useState<number>(now.getMonth());
434
+ const [selectedDay, setSelectedDay] = useState<number>(now.getDate());
435
+ const [selectedYear, setSelectedYear] = useState<number>(now.getFullYear());
436
+
437
+ const months = [
438
+ "January", "February", "March", "April", "May", "June",
439
+ "July", "August", "September", "October", "November", "December"
440
+ ];
441
+
442
+ const generateDayOptions = (month: number, year: number) => {
443
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
444
+ const options: React.ReactNode[] = [];
445
+ for (let i = 1; i <= daysInMonth; i++) {
446
+ options.push(<PickerComponent.Item key={i} label={i.toString()} value={i} />);
447
+ }
448
+ return options;
449
+ };
450
+
451
+ const generateYearOptions = () => {
452
+ const currentYear = new Date().getFullYear();
453
+ const options: React.ReactNode[] = [];
454
+ for (let i = currentYear; i >= currentYear - 100; i--) {
455
+ options.push(<PickerComponent.Item key={i} label={i.toString()} value={i} />);
456
+ }
457
+ return options;
458
+ };
459
+
460
+ const handleMonthChange = (month: number) => {
461
+ setSelectedMonth(month);
462
+ // Adjust day if it exceeds the new month's days
463
+ const daysInNewMonth = new Date(selectedYear, month + 1, 0).getDate();
464
+ if (selectedDay > daysInNewMonth) {
465
+ setSelectedDay(daysInNewMonth);
466
+ }
467
+ };
468
+
469
+ const handleContinue = () => {
470
+ // Return date as ISO string "YYYY-MM-DD"
471
+ const date = new Date(selectedYear, selectedMonth, selectedDay);
472
+ onContinue(date.toISOString().split('T')[0]);
473
+ };
474
+
475
+ return (
476
+ <OnboardingTemplate
477
+ step={step}
478
+ onContinue={handleContinue}
479
+ theme={theme}
480
+ button={{ text: step.continueButtonLabel }}
481
+ >
482
+ <View style={styles.container}>
483
+ <View style={styles.textContainer}>
484
+ <Text style={[getTextStyle(theme, "heading1"), styles.title, { color: theme.colors.text.primary }]}>
485
+ {title}
486
+ </Text>
487
+ {description && (
488
+ <Text style={[getTextStyle(theme, "body"), styles.description, { color: theme.colors.text.secondary }]}>
489
+ {description}
490
+ </Text>
491
+ )}
492
+ </View>
493
+
494
+ <View style={styles.pickerContainer}>
495
+ <View style={styles.datePickerRow}>
496
+ {/* Month Picker */}
497
+ <PickerComponent
498
+ selectedValue={selectedMonth}
499
+ onValueChange={(itemValue: number) => handleMonthChange(itemValue)}
500
+ style={styles.monthPicker}
501
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
502
+ >
503
+ {months.map((month, index) => (
504
+ <PickerComponent.Item key={index} label={month} value={index} />
505
+ ))}
506
+ </PickerComponent>
507
+
508
+ {/* Day Picker */}
509
+ <PickerComponent
510
+ selectedValue={selectedDay}
511
+ onValueChange={(itemValue: number) => setSelectedDay(itemValue)}
512
+ style={styles.dayPicker}
513
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
514
+ >
515
+ {generateDayOptions(selectedMonth, selectedYear)}
516
+ </PickerComponent>
517
+
518
+ {/* Year Picker */}
519
+ <PickerComponent
520
+ selectedValue={selectedYear}
521
+ onValueChange={(itemValue: number) => setSelectedYear(itemValue)}
522
+ style={styles.yearPicker}
523
+ itemStyle={[styles.pickerItem, { color: theme.colors.text.primary }]}
524
+ >
525
+ {generateYearOptions()}
526
+ </PickerComponent>
527
+ </View>
528
+ </View>
529
+ </View>
530
+ </OnboardingTemplate>
531
+ );
532
+ };
533
+
534
+ const styles = StyleSheet.create({
535
+ container: {
536
+ flex: 1,
537
+ paddingHorizontal: 24,
538
+ paddingTop: 20,
539
+ gap: 32,
540
+ },
541
+ textContainer: {
542
+ gap: 16,
543
+ },
544
+ title: {
545
+ textAlign: "center",
546
+ letterSpacing: -0.76,
547
+ },
548
+ description: {
549
+ textAlign: "center",
550
+ },
551
+ placeholderContainer: {
552
+ flex: 1,
553
+ justifyContent: "center",
554
+ alignItems: "center",
555
+ borderRadius: 24,
556
+ padding: 32,
557
+ },
558
+ placeholderText: {
559
+ textAlign: "center",
560
+ },
561
+ pickerContainer: {
562
+ flex: 1,
563
+ justifyContent: "center",
564
+ },
565
+ pickerRow: {
566
+ flexDirection: "row",
567
+ },
568
+ weightPicker: {
569
+ width: "65%",
570
+ },
571
+ unitPicker: {
572
+ width: "35%",
573
+ },
574
+ pickerItem: {
575
+ fontSize: 20,
576
+ },
577
+ heightImperialContainer: {
578
+ flex: 1,
579
+ },
580
+ heightFeetPicker: {
581
+ width: "33%",
582
+ },
583
+ heightInchesPicker: {
584
+ width: "33%",
585
+ },
586
+ heightUnitPicker: {
587
+ width: "34%",
588
+ },
589
+ nameInputContainer: {
590
+ flex: 1,
591
+ justifyContent: "center",
592
+ paddingHorizontal: 8,
593
+ },
594
+ nameInput: {
595
+ width: "100%",
596
+ paddingVertical: 16,
597
+ paddingHorizontal: 24,
598
+ height: 70,
599
+ fontSize: 17,
600
+ borderRadius: 16,
601
+ },
602
+ datePickerRow: {
603
+ flexDirection: "row",
604
+ },
605
+ monthPicker: {
606
+ flex: 2,
607
+ },
608
+ dayPicker: {
609
+ flex: 1,
610
+ },
611
+ yearPicker: {
612
+ flex: 1,
613
+ },
614
+ });
615
+
616
+ import { withErrorBoundary } from '../../ErrorBoundary';
617
+
618
+ export const PickerRenderer = withErrorBoundary(PickerRendererBase, 'Picker');
@@ -0,0 +1,2 @@
1
+ export * from "./Renderer";
2
+ export * from "./types";
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import { CustomPayloadSchema } from "../types";
3
+
4
+ export const PickerTypeEnum = z.enum([
5
+ "height",
6
+ "weight",
7
+ "age",
8
+ "date",
9
+ "gender",
10
+ "coach",
11
+ "name",
12
+ ]);
13
+
14
+ export const PickerStepPayloadSchema = z.object({
15
+ title: z.string(),
16
+ description: z.string().nullish(),
17
+ pickerType: z.union([PickerTypeEnum, z.string()]),
18
+ });
19
+
20
+ export const PickerStepTypeSchema = z.object({
21
+ id: z.string(),
22
+ type: z.literal("Picker"),
23
+ name: z.string(),
24
+ displayProgressHeader: z.boolean(),
25
+ payload: PickerStepPayloadSchema,
26
+ customPayload: CustomPayloadSchema,
27
+ continueButtonLabel: z.string().optional().default("Continue"),
28
+ figmaUrl: z.string().nullish(),
29
+ });
30
+
31
+ export type PickerStepType = z.infer<typeof PickerStepTypeSchema>;
32
+
33
+ export type WeightUnit = "kg" | "lb";
34
+ export type HeightUnit = "cm" | "ft";