@croacroa/react-native-template 1.0.0 → 2.0.1

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 (70) hide show
  1. package/.github/workflows/ci.yml +187 -184
  2. package/.github/workflows/eas-build.yml +55 -55
  3. package/.github/workflows/eas-update.yml +50 -50
  4. package/CHANGELOG.md +106 -106
  5. package/CONTRIBUTING.md +377 -377
  6. package/README.md +399 -399
  7. package/__tests__/components/snapshots.test.tsx +131 -0
  8. package/__tests__/integration/auth-api.test.tsx +227 -0
  9. package/__tests__/performance/VirtualizedList.perf.test.tsx +362 -0
  10. package/app/(public)/onboarding.tsx +5 -5
  11. package/app.config.ts +45 -2
  12. package/assets/images/.gitkeep +7 -7
  13. package/components/onboarding/OnboardingScreen.tsx +370 -370
  14. package/components/onboarding/index.ts +2 -2
  15. package/components/providers/SuspenseBoundary.tsx +357 -0
  16. package/components/providers/index.ts +21 -0
  17. package/components/ui/Avatar.tsx +316 -316
  18. package/components/ui/Badge.tsx +416 -416
  19. package/components/ui/BottomSheet.tsx +307 -307
  20. package/components/ui/Checkbox.tsx +261 -261
  21. package/components/ui/OptimizedImage.tsx +369 -369
  22. package/components/ui/Select.tsx +240 -240
  23. package/components/ui/VirtualizedList.tsx +285 -0
  24. package/components/ui/index.ts +23 -18
  25. package/constants/config.ts +97 -54
  26. package/docs/adr/001-state-management.md +79 -79
  27. package/docs/adr/002-styling-approach.md +130 -130
  28. package/docs/adr/003-data-fetching.md +155 -155
  29. package/docs/adr/004-auth-adapter-pattern.md +144 -144
  30. package/docs/adr/README.md +78 -78
  31. package/hooks/index.ts +27 -25
  32. package/hooks/useApi.ts +102 -5
  33. package/hooks/useAuth.tsx +82 -0
  34. package/hooks/useBiometrics.ts +295 -295
  35. package/hooks/useDeepLinking.ts +256 -256
  36. package/hooks/useMFA.ts +499 -0
  37. package/hooks/useNotifications.ts +39 -0
  38. package/hooks/useOffline.ts +60 -6
  39. package/hooks/usePerformance.ts +434 -434
  40. package/hooks/useTheme.tsx +76 -0
  41. package/hooks/useUpdates.ts +358 -358
  42. package/i18n/index.ts +194 -77
  43. package/i18n/locales/ar.json +101 -0
  44. package/i18n/locales/de.json +101 -0
  45. package/i18n/locales/en.json +101 -101
  46. package/i18n/locales/es.json +101 -0
  47. package/i18n/locales/fr.json +101 -101
  48. package/jest.config.js +4 -4
  49. package/maestro/README.md +113 -113
  50. package/maestro/config.yaml +35 -35
  51. package/maestro/flows/login.yaml +62 -62
  52. package/maestro/flows/mfa-login.yaml +92 -0
  53. package/maestro/flows/mfa-setup.yaml +86 -0
  54. package/maestro/flows/navigation.yaml +68 -68
  55. package/maestro/flows/offline-conflict.yaml +101 -0
  56. package/maestro/flows/offline-sync.yaml +128 -0
  57. package/maestro/flows/offline.yaml +60 -60
  58. package/maestro/flows/register.yaml +94 -94
  59. package/package.json +175 -170
  60. package/services/analytics.ts +428 -428
  61. package/services/api.ts +340 -340
  62. package/services/authAdapter.ts +333 -333
  63. package/services/backgroundSync.ts +626 -0
  64. package/services/index.ts +54 -22
  65. package/services/security.ts +286 -0
  66. package/tailwind.config.js +47 -47
  67. package/utils/accessibility.ts +446 -446
  68. package/utils/index.ts +52 -43
  69. package/utils/validation.ts +2 -1
  70. package/utils/withAccessibility.tsx +272 -0
@@ -1,307 +1,307 @@
1
- import { forwardRef, useCallback, useMemo, ReactNode } from "react";
2
- import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
3
- import GorhomBottomSheet, {
4
- BottomSheetBackdrop,
5
- BottomSheetView,
6
- BottomSheetScrollView,
7
- BottomSheetBackdropProps,
8
- } from "@gorhom/bottom-sheet";
9
- import { Ionicons } from "@expo/vector-icons";
10
- import { useTheme } from "@/hooks/useTheme";
11
- import { cn } from "@/utils/cn";
12
-
13
- /**
14
- * Hook to control BottomSheet imperatively
15
- *
16
- * @example
17
- * ```tsx
18
- * function MyComponent() {
19
- * const { ref, open, close, snapTo } = useBottomSheet();
20
- *
21
- * return (
22
- * <>
23
- * <Button onPress={() => open()}>Open Sheet</Button>
24
- * <BottomSheet ref={ref}>
25
- * <Text>Content</Text>
26
- * </BottomSheet>
27
- * </>
28
- * );
29
- * }
30
- * ```
31
- */
32
- import { useRef } from "react";
33
-
34
- interface BottomSheetProps {
35
- /**
36
- * Content to render inside the bottom sheet
37
- */
38
- children: ReactNode;
39
-
40
- /**
41
- * Snap points for the bottom sheet (e.g., ['25%', '50%', '90%'])
42
- */
43
- snapPoints?: (string | number)[];
44
-
45
- /**
46
- * Initial snap point index
47
- */
48
- index?: number;
49
-
50
- /**
51
- * Title displayed in the header
52
- */
53
- title?: string;
54
-
55
- /**
56
- * Show close button in header
57
- */
58
- showCloseButton?: boolean;
59
-
60
- /**
61
- * Called when the sheet is closed
62
- */
63
- onClose?: () => void;
64
-
65
- /**
66
- * Called when the sheet index changes
67
- */
68
- onChange?: (index: number) => void;
69
-
70
- /**
71
- * Whether to enable backdrop
72
- */
73
- enableBackdrop?: boolean;
74
-
75
- /**
76
- * Whether to close on backdrop press
77
- */
78
- closeOnBackdropPress?: boolean;
79
-
80
- /**
81
- * Whether content is scrollable
82
- */
83
- scrollable?: boolean;
84
-
85
- /**
86
- * Whether to enable handle
87
- */
88
- enableHandle?: boolean;
89
-
90
- /**
91
- * Additional styles for the container
92
- */
93
- containerClassName?: string;
94
-
95
- /**
96
- * Additional styles for the content
97
- */
98
- contentClassName?: string;
99
- }
100
-
101
- export type BottomSheetRef = GorhomBottomSheet;
102
-
103
- export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
104
- (
105
- {
106
- children,
107
- snapPoints: customSnapPoints,
108
- index = -1,
109
- title,
110
- showCloseButton = true,
111
- onClose,
112
- onChange,
113
- enableBackdrop = true,
114
- closeOnBackdropPress = true,
115
- scrollable = false,
116
- enableHandle = true,
117
- containerClassName,
118
- contentClassName,
119
- },
120
- ref
121
- ) => {
122
- const { isDark } = useTheme();
123
-
124
- // Default snap points
125
- const snapPoints = useMemo(
126
- () => customSnapPoints || ["50%", "90%"],
127
- [customSnapPoints]
128
- );
129
-
130
- // Handle sheet changes
131
- const handleSheetChanges = useCallback(
132
- (sheetIndex: number) => {
133
- onChange?.(sheetIndex);
134
- if (sheetIndex === -1) {
135
- onClose?.();
136
- }
137
- },
138
- [onChange, onClose]
139
- );
140
-
141
- // Handle close button press
142
- const handleClose = useCallback(() => {
143
- if (ref && "current" in ref && ref.current) {
144
- ref.current.close();
145
- }
146
- onClose?.();
147
- }, [ref, onClose]);
148
-
149
- // Backdrop component
150
- const renderBackdrop = useCallback(
151
- (props: BottomSheetBackdropProps) => (
152
- <BottomSheetBackdrop
153
- {...props}
154
- disappearsOnIndex={-1}
155
- appearsOnIndex={0}
156
- pressBehavior={closeOnBackdropPress ? "close" : "none"}
157
- opacity={0.5}
158
- />
159
- ),
160
- [closeOnBackdropPress]
161
- );
162
-
163
- // Handle component
164
- const renderHandle = useCallback(() => {
165
- if (!enableHandle) return null;
166
-
167
- return (
168
- <View className="items-center pt-2 pb-1">
169
- <View
170
- className={cn(
171
- "w-10 h-1 rounded-full",
172
- isDark ? "bg-gray-600" : "bg-gray-300"
173
- )}
174
- />
175
- </View>
176
- );
177
- }, [enableHandle, isDark]);
178
-
179
- const ContentWrapper = scrollable ? BottomSheetScrollView : BottomSheetView;
180
-
181
- return (
182
- <GorhomBottomSheet
183
- ref={ref}
184
- index={index}
185
- snapPoints={snapPoints}
186
- onChange={handleSheetChanges}
187
- backdropComponent={enableBackdrop ? renderBackdrop : null}
188
- handleComponent={renderHandle}
189
- enablePanDownToClose
190
- backgroundStyle={[
191
- styles.background,
192
- { backgroundColor: isDark ? "#1e293b" : "#ffffff" },
193
- ]}
194
- style={styles.sheet}
195
- >
196
- <View
197
- className={cn(
198
- "flex-1",
199
- isDark ? "bg-surface-dark" : "bg-white",
200
- containerClassName
201
- )}
202
- >
203
- {/* Header */}
204
- {(title || showCloseButton) && (
205
- <View
206
- className={cn(
207
- "flex-row items-center justify-between px-4 py-3 border-b",
208
- isDark ? "border-gray-700" : "border-gray-100"
209
- )}
210
- >
211
- <Text
212
- className={cn(
213
- "text-lg font-semibold",
214
- isDark ? "text-text-dark" : "text-text-light"
215
- )}
216
- >
217
- {title || ""}
218
- </Text>
219
- {showCloseButton && (
220
- <TouchableOpacity
221
- onPress={handleClose}
222
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
223
- className="p-1"
224
- >
225
- <Ionicons
226
- name="close"
227
- size={24}
228
- color={isDark ? "#f8fafc" : "#0f172a"}
229
- />
230
- </TouchableOpacity>
231
- )}
232
- </View>
233
- )}
234
-
235
- {/* Content */}
236
- <ContentWrapper
237
- style={styles.contentContainer}
238
- contentContainerStyle={[
239
- styles.content,
240
- scrollable && styles.scrollContent,
241
- ]}
242
- >
243
- <View className={cn("flex-1", contentClassName)}>{children}</View>
244
- </ContentWrapper>
245
- </View>
246
- </GorhomBottomSheet>
247
- );
248
- }
249
- );
250
-
251
- BottomSheet.displayName = "BottomSheet";
252
-
253
- const styles = StyleSheet.create({
254
- sheet: {
255
- shadowColor: "#000",
256
- shadowOffset: { width: 0, height: -4 },
257
- shadowOpacity: 0.1,
258
- shadowRadius: 8,
259
- elevation: 5,
260
- },
261
- background: {
262
- borderTopLeftRadius: 24,
263
- borderTopRightRadius: 24,
264
- },
265
- contentContainer: {
266
- flex: 1,
267
- },
268
- content: {
269
- flex: 1,
270
- },
271
- scrollContent: {
272
- paddingBottom: 40,
273
- },
274
- });
275
-
276
- export function useBottomSheet() {
277
- const ref = useRef<BottomSheetRef>(null);
278
-
279
- const open = useCallback((snapIndex = 0) => {
280
- ref.current?.snapToIndex(snapIndex);
281
- }, []);
282
-
283
- const close = useCallback(() => {
284
- ref.current?.close();
285
- }, []);
286
-
287
- const snapTo = useCallback((index: number) => {
288
- ref.current?.snapToIndex(index);
289
- }, []);
290
-
291
- const expand = useCallback(() => {
292
- ref.current?.expand();
293
- }, []);
294
-
295
- const collapse = useCallback(() => {
296
- ref.current?.collapse();
297
- }, []);
298
-
299
- return {
300
- ref,
301
- open,
302
- close,
303
- snapTo,
304
- expand,
305
- collapse,
306
- };
307
- }
1
+ import { forwardRef, useCallback, useMemo, ReactNode } from "react";
2
+ import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
3
+ import GorhomBottomSheet, {
4
+ BottomSheetBackdrop,
5
+ BottomSheetView,
6
+ BottomSheetScrollView,
7
+ BottomSheetBackdropProps,
8
+ } from "@gorhom/bottom-sheet";
9
+ import { Ionicons } from "@expo/vector-icons";
10
+ import { useTheme } from "@/hooks/useTheme";
11
+ import { cn } from "@/utils/cn";
12
+
13
+ /**
14
+ * Hook to control BottomSheet imperatively
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * function MyComponent() {
19
+ * const { ref, open, close, snapTo } = useBottomSheet();
20
+ *
21
+ * return (
22
+ * <>
23
+ * <Button onPress={() => open()}>Open Sheet</Button>
24
+ * <BottomSheet ref={ref}>
25
+ * <Text>Content</Text>
26
+ * </BottomSheet>
27
+ * </>
28
+ * );
29
+ * }
30
+ * ```
31
+ */
32
+ import { useRef } from "react";
33
+
34
+ interface BottomSheetProps {
35
+ /**
36
+ * Content to render inside the bottom sheet
37
+ */
38
+ children: ReactNode;
39
+
40
+ /**
41
+ * Snap points for the bottom sheet (e.g., ['25%', '50%', '90%'])
42
+ */
43
+ snapPoints?: (string | number)[];
44
+
45
+ /**
46
+ * Initial snap point index
47
+ */
48
+ index?: number;
49
+
50
+ /**
51
+ * Title displayed in the header
52
+ */
53
+ title?: string;
54
+
55
+ /**
56
+ * Show close button in header
57
+ */
58
+ showCloseButton?: boolean;
59
+
60
+ /**
61
+ * Called when the sheet is closed
62
+ */
63
+ onClose?: () => void;
64
+
65
+ /**
66
+ * Called when the sheet index changes
67
+ */
68
+ onChange?: (index: number) => void;
69
+
70
+ /**
71
+ * Whether to enable backdrop
72
+ */
73
+ enableBackdrop?: boolean;
74
+
75
+ /**
76
+ * Whether to close on backdrop press
77
+ */
78
+ closeOnBackdropPress?: boolean;
79
+
80
+ /**
81
+ * Whether content is scrollable
82
+ */
83
+ scrollable?: boolean;
84
+
85
+ /**
86
+ * Whether to enable handle
87
+ */
88
+ enableHandle?: boolean;
89
+
90
+ /**
91
+ * Additional styles for the container
92
+ */
93
+ containerClassName?: string;
94
+
95
+ /**
96
+ * Additional styles for the content
97
+ */
98
+ contentClassName?: string;
99
+ }
100
+
101
+ export type BottomSheetRef = GorhomBottomSheet;
102
+
103
+ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
104
+ (
105
+ {
106
+ children,
107
+ snapPoints: customSnapPoints,
108
+ index = -1,
109
+ title,
110
+ showCloseButton = true,
111
+ onClose,
112
+ onChange,
113
+ enableBackdrop = true,
114
+ closeOnBackdropPress = true,
115
+ scrollable = false,
116
+ enableHandle = true,
117
+ containerClassName,
118
+ contentClassName,
119
+ },
120
+ ref
121
+ ) => {
122
+ const { isDark } = useTheme();
123
+
124
+ // Default snap points
125
+ const snapPoints = useMemo(
126
+ () => customSnapPoints || ["50%", "90%"],
127
+ [customSnapPoints]
128
+ );
129
+
130
+ // Handle sheet changes
131
+ const handleSheetChanges = useCallback(
132
+ (sheetIndex: number) => {
133
+ onChange?.(sheetIndex);
134
+ if (sheetIndex === -1) {
135
+ onClose?.();
136
+ }
137
+ },
138
+ [onChange, onClose]
139
+ );
140
+
141
+ // Handle close button press
142
+ const handleClose = useCallback(() => {
143
+ if (ref && "current" in ref && ref.current) {
144
+ ref.current.close();
145
+ }
146
+ onClose?.();
147
+ }, [ref, onClose]);
148
+
149
+ // Backdrop component
150
+ const renderBackdrop = useCallback(
151
+ (props: BottomSheetBackdropProps) => (
152
+ <BottomSheetBackdrop
153
+ {...props}
154
+ disappearsOnIndex={-1}
155
+ appearsOnIndex={0}
156
+ pressBehavior={closeOnBackdropPress ? "close" : "none"}
157
+ opacity={0.5}
158
+ />
159
+ ),
160
+ [closeOnBackdropPress]
161
+ );
162
+
163
+ // Handle component
164
+ const renderHandle = useCallback(() => {
165
+ if (!enableHandle) return null;
166
+
167
+ return (
168
+ <View className="items-center pt-2 pb-1">
169
+ <View
170
+ className={cn(
171
+ "w-10 h-1 rounded-full",
172
+ isDark ? "bg-gray-600" : "bg-gray-300"
173
+ )}
174
+ />
175
+ </View>
176
+ );
177
+ }, [enableHandle, isDark]);
178
+
179
+ const ContentWrapper = scrollable ? BottomSheetScrollView : BottomSheetView;
180
+
181
+ return (
182
+ <GorhomBottomSheet
183
+ ref={ref}
184
+ index={index}
185
+ snapPoints={snapPoints}
186
+ onChange={handleSheetChanges}
187
+ backdropComponent={enableBackdrop ? renderBackdrop : null}
188
+ handleComponent={renderHandle}
189
+ enablePanDownToClose
190
+ backgroundStyle={[
191
+ styles.background,
192
+ { backgroundColor: isDark ? "#1e293b" : "#ffffff" },
193
+ ]}
194
+ style={styles.sheet}
195
+ >
196
+ <View
197
+ className={cn(
198
+ "flex-1",
199
+ isDark ? "bg-surface-dark" : "bg-white",
200
+ containerClassName
201
+ )}
202
+ >
203
+ {/* Header */}
204
+ {(title || showCloseButton) && (
205
+ <View
206
+ className={cn(
207
+ "flex-row items-center justify-between px-4 py-3 border-b",
208
+ isDark ? "border-gray-700" : "border-gray-100"
209
+ )}
210
+ >
211
+ <Text
212
+ className={cn(
213
+ "text-lg font-semibold",
214
+ isDark ? "text-text-dark" : "text-text-light"
215
+ )}
216
+ >
217
+ {title || ""}
218
+ </Text>
219
+ {showCloseButton && (
220
+ <TouchableOpacity
221
+ onPress={handleClose}
222
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
223
+ className="p-1"
224
+ >
225
+ <Ionicons
226
+ name="close"
227
+ size={24}
228
+ color={isDark ? "#f8fafc" : "#0f172a"}
229
+ />
230
+ </TouchableOpacity>
231
+ )}
232
+ </View>
233
+ )}
234
+
235
+ {/* Content */}
236
+ <ContentWrapper
237
+ style={styles.contentContainer}
238
+ contentContainerStyle={[
239
+ styles.content,
240
+ scrollable && styles.scrollContent,
241
+ ]}
242
+ >
243
+ <View className={cn("flex-1", contentClassName)}>{children}</View>
244
+ </ContentWrapper>
245
+ </View>
246
+ </GorhomBottomSheet>
247
+ );
248
+ }
249
+ );
250
+
251
+ BottomSheet.displayName = "BottomSheet";
252
+
253
+ const styles = StyleSheet.create({
254
+ sheet: {
255
+ shadowColor: "#000",
256
+ shadowOffset: { width: 0, height: -4 },
257
+ shadowOpacity: 0.1,
258
+ shadowRadius: 8,
259
+ elevation: 5,
260
+ },
261
+ background: {
262
+ borderTopLeftRadius: 24,
263
+ borderTopRightRadius: 24,
264
+ },
265
+ contentContainer: {
266
+ flex: 1,
267
+ },
268
+ content: {
269
+ flex: 1,
270
+ },
271
+ scrollContent: {
272
+ paddingBottom: 40,
273
+ },
274
+ });
275
+
276
+ export function useBottomSheet() {
277
+ const ref = useRef<BottomSheetRef>(null);
278
+
279
+ const open = useCallback((snapIndex = 0) => {
280
+ ref.current?.snapToIndex(snapIndex);
281
+ }, []);
282
+
283
+ const close = useCallback(() => {
284
+ ref.current?.close();
285
+ }, []);
286
+
287
+ const snapTo = useCallback((index: number) => {
288
+ ref.current?.snapToIndex(index);
289
+ }, []);
290
+
291
+ const expand = useCallback(() => {
292
+ ref.current?.expand();
293
+ }, []);
294
+
295
+ const collapse = useCallback(() => {
296
+ ref.current?.collapse();
297
+ }, []);
298
+
299
+ return {
300
+ ref,
301
+ open,
302
+ close,
303
+ snapTo,
304
+ expand,
305
+ collapse,
306
+ };
307
+ }