@croacroa/react-native-template 1.0.0 → 2.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 (69) 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 +13 -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 +32 -2
  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 +229 -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/withAccessibility.tsx +272 -0
@@ -1,261 +1,261 @@
1
- import { TouchableOpacity, View, Text } from "react-native";
2
- import Animated, {
3
- useAnimatedStyle,
4
- withSpring,
5
- withTiming,
6
- interpolateColor,
7
- } from "react-native-reanimated";
8
- import { Ionicons } from "@expo/vector-icons";
9
- import { useTheme } from "@/hooks/useTheme";
10
- import { cn } from "@/utils/cn";
11
-
12
- interface CheckboxProps {
13
- /**
14
- * Whether the checkbox is checked
15
- */
16
- checked: boolean;
17
-
18
- /**
19
- * Callback when the checkbox is toggled
20
- */
21
- onChange: (checked: boolean) => void;
22
-
23
- /**
24
- * Label text
25
- */
26
- label?: string;
27
-
28
- /**
29
- * Description text (below label)
30
- */
31
- description?: string;
32
-
33
- /**
34
- * Whether the checkbox is disabled
35
- */
36
- disabled?: boolean;
37
-
38
- /**
39
- * Size variant
40
- */
41
- size?: "sm" | "md" | "lg";
42
-
43
- /**
44
- * Additional class name
45
- */
46
- className?: string;
47
-
48
- /**
49
- * Error message
50
- */
51
- error?: string;
52
- }
53
-
54
- const AnimatedView = Animated.createAnimatedComponent(View);
55
-
56
- const sizes = {
57
- sm: { box: 18, icon: 12, label: "text-sm" },
58
- md: { box: 22, icon: 16, label: "text-base" },
59
- lg: { box: 26, icon: 20, label: "text-lg" },
60
- };
61
-
62
- export function Checkbox({
63
- checked,
64
- onChange,
65
- label,
66
- description,
67
- disabled = false,
68
- size = "md",
69
- className,
70
- error,
71
- }: CheckboxProps) {
72
- const { isDark } = useTheme();
73
- const sizeConfig = sizes[size];
74
-
75
- const animatedBoxStyle = useAnimatedStyle(() => {
76
- const backgroundColor = interpolateColor(
77
- checked ? 1 : 0,
78
- [0, 1],
79
- ["transparent", "#10b981"]
80
- );
81
-
82
- const borderColor = interpolateColor(
83
- checked ? 1 : 0,
84
- [0, 1],
85
- [error ? "#ef4444" : isDark ? "#475569" : "#cbd5e1", "#10b981"]
86
- );
87
-
88
- return {
89
- backgroundColor: withTiming(backgroundColor, { duration: 150 }),
90
- borderColor: withTiming(borderColor, { duration: 150 }),
91
- transform: [
92
- {
93
- scale: withSpring(checked ? 1 : 0.95, {
94
- damping: 15,
95
- stiffness: 400,
96
- }),
97
- },
98
- ],
99
- };
100
- }, [checked, isDark, error]);
101
-
102
- const animatedCheckStyle = useAnimatedStyle(() => {
103
- return {
104
- opacity: withTiming(checked ? 1 : 0, { duration: 150 }),
105
- transform: [
106
- {
107
- scale: withSpring(checked ? 1 : 0.5, {
108
- damping: 15,
109
- stiffness: 400,
110
- }),
111
- },
112
- ],
113
- };
114
- }, [checked]);
115
-
116
- return (
117
- <TouchableOpacity
118
- onPress={() => !disabled && onChange(!checked)}
119
- disabled={disabled}
120
- activeOpacity={0.7}
121
- className={cn(
122
- "flex-row items-start",
123
- disabled && "opacity-50",
124
- className
125
- )}
126
- >
127
- <AnimatedView
128
- style={[
129
- animatedBoxStyle,
130
- {
131
- width: sizeConfig.box,
132
- height: sizeConfig.box,
133
- borderWidth: 2,
134
- borderRadius: 6,
135
- justifyContent: "center",
136
- alignItems: "center",
137
- marginTop: 2,
138
- },
139
- ]}
140
- >
141
- <Animated.View style={animatedCheckStyle}>
142
- <Ionicons name="checkmark" size={sizeConfig.icon} color="white" />
143
- </Animated.View>
144
- </AnimatedView>
145
-
146
- {(label || description) && (
147
- <View className="flex-1 ml-3">
148
- {label && (
149
- <Text
150
- className={cn(
151
- sizeConfig.label,
152
- isDark ? "text-text-dark" : "text-text-light",
153
- disabled && "opacity-70"
154
- )}
155
- >
156
- {label}
157
- </Text>
158
- )}
159
- {description && (
160
- <Text
161
- className={cn(
162
- "text-sm mt-0.5",
163
- isDark ? "text-muted-dark" : "text-muted-light"
164
- )}
165
- >
166
- {description}
167
- </Text>
168
- )}
169
- {error && <Text className="text-red-500 text-sm mt-1">{error}</Text>}
170
- </View>
171
- )}
172
- </TouchableOpacity>
173
- );
174
- }
175
-
176
- /**
177
- * Checkbox Group component for multiple checkboxes
178
- */
179
- interface CheckboxGroupProps<T extends string> {
180
- /**
181
- * Available options
182
- */
183
- options: {
184
- value: T;
185
- label: string;
186
- description?: string;
187
- disabled?: boolean;
188
- }[];
189
-
190
- /**
191
- * Currently selected values
192
- */
193
- value: T[];
194
-
195
- /**
196
- * Callback when selection changes
197
- */
198
- onChange: (value: T[]) => void;
199
-
200
- /**
201
- * Group label
202
- */
203
- label?: string;
204
-
205
- /**
206
- * Size variant
207
- */
208
- size?: "sm" | "md" | "lg";
209
-
210
- /**
211
- * Additional class name
212
- */
213
- className?: string;
214
- }
215
-
216
- export function CheckboxGroup<T extends string>({
217
- options,
218
- value,
219
- onChange,
220
- label,
221
- size = "md",
222
- className,
223
- }: CheckboxGroupProps<T>) {
224
- const { isDark } = useTheme();
225
-
226
- const handleToggle = (optionValue: T) => {
227
- if (value.includes(optionValue)) {
228
- onChange(value.filter((v) => v !== optionValue));
229
- } else {
230
- onChange([...value, optionValue]);
231
- }
232
- };
233
-
234
- return (
235
- <View className={className}>
236
- {label && (
237
- <Text
238
- className={cn(
239
- "text-sm font-medium mb-3",
240
- isDark ? "text-text-dark" : "text-text-light"
241
- )}
242
- >
243
- {label}
244
- </Text>
245
- )}
246
- <View className="gap-3">
247
- {options.map((option) => (
248
- <Checkbox
249
- key={option.value}
250
- checked={value.includes(option.value)}
251
- onChange={() => handleToggle(option.value)}
252
- label={option.label}
253
- description={option.description}
254
- disabled={option.disabled}
255
- size={size}
256
- />
257
- ))}
258
- </View>
259
- </View>
260
- );
261
- }
1
+ import { TouchableOpacity, View, Text } from "react-native";
2
+ import Animated, {
3
+ useAnimatedStyle,
4
+ withSpring,
5
+ withTiming,
6
+ interpolateColor,
7
+ } from "react-native-reanimated";
8
+ import { Ionicons } from "@expo/vector-icons";
9
+ import { useTheme } from "@/hooks/useTheme";
10
+ import { cn } from "@/utils/cn";
11
+
12
+ interface CheckboxProps {
13
+ /**
14
+ * Whether the checkbox is checked
15
+ */
16
+ checked: boolean;
17
+
18
+ /**
19
+ * Callback when the checkbox is toggled
20
+ */
21
+ onChange: (checked: boolean) => void;
22
+
23
+ /**
24
+ * Label text
25
+ */
26
+ label?: string;
27
+
28
+ /**
29
+ * Description text (below label)
30
+ */
31
+ description?: string;
32
+
33
+ /**
34
+ * Whether the checkbox is disabled
35
+ */
36
+ disabled?: boolean;
37
+
38
+ /**
39
+ * Size variant
40
+ */
41
+ size?: "sm" | "md" | "lg";
42
+
43
+ /**
44
+ * Additional class name
45
+ */
46
+ className?: string;
47
+
48
+ /**
49
+ * Error message
50
+ */
51
+ error?: string;
52
+ }
53
+
54
+ const AnimatedView = Animated.createAnimatedComponent(View);
55
+
56
+ const sizes = {
57
+ sm: { box: 18, icon: 12, label: "text-sm" },
58
+ md: { box: 22, icon: 16, label: "text-base" },
59
+ lg: { box: 26, icon: 20, label: "text-lg" },
60
+ };
61
+
62
+ export function Checkbox({
63
+ checked,
64
+ onChange,
65
+ label,
66
+ description,
67
+ disabled = false,
68
+ size = "md",
69
+ className,
70
+ error,
71
+ }: CheckboxProps) {
72
+ const { isDark } = useTheme();
73
+ const sizeConfig = sizes[size];
74
+
75
+ const animatedBoxStyle = useAnimatedStyle(() => {
76
+ const backgroundColor = interpolateColor(
77
+ checked ? 1 : 0,
78
+ [0, 1],
79
+ ["transparent", "#10b981"]
80
+ );
81
+
82
+ const borderColor = interpolateColor(
83
+ checked ? 1 : 0,
84
+ [0, 1],
85
+ [error ? "#ef4444" : isDark ? "#475569" : "#cbd5e1", "#10b981"]
86
+ );
87
+
88
+ return {
89
+ backgroundColor: withTiming(backgroundColor, { duration: 150 }),
90
+ borderColor: withTiming(borderColor, { duration: 150 }),
91
+ transform: [
92
+ {
93
+ scale: withSpring(checked ? 1 : 0.95, {
94
+ damping: 15,
95
+ stiffness: 400,
96
+ }),
97
+ },
98
+ ],
99
+ };
100
+ }, [checked, isDark, error]);
101
+
102
+ const animatedCheckStyle = useAnimatedStyle(() => {
103
+ return {
104
+ opacity: withTiming(checked ? 1 : 0, { duration: 150 }),
105
+ transform: [
106
+ {
107
+ scale: withSpring(checked ? 1 : 0.5, {
108
+ damping: 15,
109
+ stiffness: 400,
110
+ }),
111
+ },
112
+ ],
113
+ };
114
+ }, [checked]);
115
+
116
+ return (
117
+ <TouchableOpacity
118
+ onPress={() => !disabled && onChange(!checked)}
119
+ disabled={disabled}
120
+ activeOpacity={0.7}
121
+ className={cn(
122
+ "flex-row items-start",
123
+ disabled && "opacity-50",
124
+ className
125
+ )}
126
+ >
127
+ <AnimatedView
128
+ style={[
129
+ animatedBoxStyle,
130
+ {
131
+ width: sizeConfig.box,
132
+ height: sizeConfig.box,
133
+ borderWidth: 2,
134
+ borderRadius: 6,
135
+ justifyContent: "center",
136
+ alignItems: "center",
137
+ marginTop: 2,
138
+ },
139
+ ]}
140
+ >
141
+ <Animated.View style={animatedCheckStyle}>
142
+ <Ionicons name="checkmark" size={sizeConfig.icon} color="white" />
143
+ </Animated.View>
144
+ </AnimatedView>
145
+
146
+ {(label || description) && (
147
+ <View className="flex-1 ml-3">
148
+ {label && (
149
+ <Text
150
+ className={cn(
151
+ sizeConfig.label,
152
+ isDark ? "text-text-dark" : "text-text-light",
153
+ disabled && "opacity-70"
154
+ )}
155
+ >
156
+ {label}
157
+ </Text>
158
+ )}
159
+ {description && (
160
+ <Text
161
+ className={cn(
162
+ "text-sm mt-0.5",
163
+ isDark ? "text-muted-dark" : "text-muted-light"
164
+ )}
165
+ >
166
+ {description}
167
+ </Text>
168
+ )}
169
+ {error && <Text className="text-red-500 text-sm mt-1">{error}</Text>}
170
+ </View>
171
+ )}
172
+ </TouchableOpacity>
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Checkbox Group component for multiple checkboxes
178
+ */
179
+ interface CheckboxGroupProps<T extends string> {
180
+ /**
181
+ * Available options
182
+ */
183
+ options: {
184
+ value: T;
185
+ label: string;
186
+ description?: string;
187
+ disabled?: boolean;
188
+ }[];
189
+
190
+ /**
191
+ * Currently selected values
192
+ */
193
+ value: T[];
194
+
195
+ /**
196
+ * Callback when selection changes
197
+ */
198
+ onChange: (value: T[]) => void;
199
+
200
+ /**
201
+ * Group label
202
+ */
203
+ label?: string;
204
+
205
+ /**
206
+ * Size variant
207
+ */
208
+ size?: "sm" | "md" | "lg";
209
+
210
+ /**
211
+ * Additional class name
212
+ */
213
+ className?: string;
214
+ }
215
+
216
+ export function CheckboxGroup<T extends string>({
217
+ options,
218
+ value,
219
+ onChange,
220
+ label,
221
+ size = "md",
222
+ className,
223
+ }: CheckboxGroupProps<T>) {
224
+ const { isDark } = useTheme();
225
+
226
+ const handleToggle = (optionValue: T) => {
227
+ if (value.includes(optionValue)) {
228
+ onChange(value.filter((v) => v !== optionValue));
229
+ } else {
230
+ onChange([...value, optionValue]);
231
+ }
232
+ };
233
+
234
+ return (
235
+ <View className={className}>
236
+ {label && (
237
+ <Text
238
+ className={cn(
239
+ "text-sm font-medium mb-3",
240
+ isDark ? "text-text-dark" : "text-text-light"
241
+ )}
242
+ >
243
+ {label}
244
+ </Text>
245
+ )}
246
+ <View className="gap-3">
247
+ {options.map((option) => (
248
+ <Checkbox
249
+ key={option.value}
250
+ checked={value.includes(option.value)}
251
+ onChange={() => handleToggle(option.value)}
252
+ label={option.label}
253
+ description={option.description}
254
+ disabled={option.disabled}
255
+ size={size}
256
+ />
257
+ ))}
258
+ </View>
259
+ </View>
260
+ );
261
+ }