@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
package/utils/index.ts CHANGED
@@ -1,43 +1,52 @@
1
- export { cn } from "./cn";
2
- export { toast, handleApiError } from "./toast";
3
- export {
4
- emailSchema,
5
- passwordSchema,
6
- nameSchema,
7
- loginSchema,
8
- registerSchema,
9
- forgotPasswordSchema,
10
- changePasswordSchema,
11
- profileSchema,
12
- } from "./validation";
13
- export type {
14
- LoginFormData,
15
- RegisterFormData,
16
- ForgotPasswordFormData,
17
- ChangePasswordFormData,
18
- ProfileFormData,
19
- } from "./validation";
20
-
21
- // Accessibility utilities
22
- export {
23
- buttonA11y,
24
- linkA11y,
25
- inputA11y,
26
- toggleA11y,
27
- headerA11y,
28
- imageA11y,
29
- listItemA11y,
30
- progressA11y,
31
- tabA11y,
32
- alertA11y,
33
- useScreenReader,
34
- useReduceMotion,
35
- useBoldText,
36
- useAccessibilityPreferences,
37
- announce,
38
- setAccessibilityFocus,
39
- formatPriceA11y,
40
- formatDateA11y,
41
- formatDurationA11y,
42
- } from "./accessibility";
43
- export type { AccessibilityProps } from "./accessibility";
1
+ export { cn } from "./cn";
2
+ export { toast, handleApiError } from "./toast";
3
+ export {
4
+ emailSchema,
5
+ passwordSchema,
6
+ nameSchema,
7
+ loginSchema,
8
+ registerSchema,
9
+ forgotPasswordSchema,
10
+ changePasswordSchema,
11
+ profileSchema,
12
+ } from "./validation";
13
+ export type {
14
+ LoginFormData,
15
+ RegisterFormData,
16
+ ForgotPasswordFormData,
17
+ ChangePasswordFormData,
18
+ ProfileFormData,
19
+ } from "./validation";
20
+
21
+ // Accessibility utilities
22
+ export {
23
+ buttonA11y,
24
+ linkA11y,
25
+ inputA11y,
26
+ toggleA11y,
27
+ headerA11y,
28
+ imageA11y,
29
+ listItemA11y,
30
+ progressA11y,
31
+ tabA11y,
32
+ alertA11y,
33
+ useScreenReader,
34
+ useReduceMotion,
35
+ useBoldText,
36
+ useAccessibilityPreferences,
37
+ announce,
38
+ setAccessibilityFocus,
39
+ formatPriceA11y,
40
+ formatDateA11y,
41
+ formatDurationA11y,
42
+ } from "./accessibility";
43
+ export type { AccessibilityProps } from "./accessibility";
44
+
45
+ // Accessibility enforcement HOC and utilities
46
+ export {
47
+ withAccessibility,
48
+ useAccessibilityValidation,
49
+ createA11yProps,
50
+ A11yContext,
51
+ auditAccessibility,
52
+ } from "./withAccessibility";
@@ -0,0 +1,272 @@
1
+ /**
2
+ * @fileoverview Accessibility enforcement HOC and utilities
3
+ * Provides HOCs and hooks to enforce accessibility props on components.
4
+ * @module utils/withAccessibility
5
+ */
6
+
7
+ import React, { ComponentType, forwardRef } from "react";
8
+ import { AccessibilityProps, AccessibilityRole } from "react-native";
9
+
10
+ /**
11
+ * Required accessibility props that must be provided
12
+ */
13
+ interface RequiredA11yProps {
14
+ accessibilityLabel: string;
15
+ accessibilityRole?: AccessibilityRole;
16
+ accessibilityHint?: string;
17
+ testID?: string;
18
+ }
19
+
20
+ /**
21
+ * A11y configuration for the HOC
22
+ */
23
+ interface A11yConfig {
24
+ /** Default role if not provided */
25
+ defaultRole?: AccessibilityRole;
26
+ /** Whether to require accessibilityHint */
27
+ requireHint?: boolean;
28
+ /** Whether to auto-generate testID from label */
29
+ autoTestID?: boolean;
30
+ /** Prefix for auto-generated testIDs */
31
+ testIDPrefix?: string;
32
+ }
33
+
34
+ /**
35
+ * Higher-Order Component that enforces accessibility props.
36
+ * Wraps a component and ensures required a11y props are provided.
37
+ *
38
+ * @param WrappedComponent - The component to wrap
39
+ * @param config - A11y configuration options
40
+ * @returns Enhanced component with a11y enforcement
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * // Create an accessible button
45
+ * const AccessibleButton = withAccessibility(Button, {
46
+ * defaultRole: 'button',
47
+ * autoTestID: true,
48
+ * testIDPrefix: 'btn',
49
+ * });
50
+ *
51
+ * // Usage - will error if accessibilityLabel is missing
52
+ * <AccessibleButton
53
+ * accessibilityLabel="Submit form"
54
+ * onPress={handleSubmit}
55
+ * >
56
+ * Submit
57
+ * </AccessibleButton>
58
+ * ```
59
+ */
60
+ export function withAccessibility<P extends AccessibilityProps>(
61
+ WrappedComponent: ComponentType<P>,
62
+ config: A11yConfig = {}
63
+ ) {
64
+ const {
65
+ defaultRole,
66
+ requireHint = false,
67
+ autoTestID = true,
68
+ testIDPrefix = "",
69
+ } = config;
70
+
71
+ type EnhancedProps = P & RequiredA11yProps;
72
+
73
+ const EnhancedComponent = forwardRef<unknown, EnhancedProps>((props, ref) => {
74
+ const {
75
+ accessibilityLabel,
76
+ accessibilityRole = defaultRole,
77
+ accessibilityHint,
78
+ testID,
79
+ ...restProps
80
+ } = props;
81
+
82
+ // Development-time validation
83
+ if (__DEV__) {
84
+ if (!accessibilityLabel) {
85
+ console.warn(
86
+ `[A11y] ${WrappedComponent.displayName || "Component"} is missing accessibilityLabel`
87
+ );
88
+ }
89
+ if (requireHint && !accessibilityHint) {
90
+ console.warn(
91
+ `[A11y] ${WrappedComponent.displayName || "Component"} is missing accessibilityHint`
92
+ );
93
+ }
94
+ }
95
+
96
+ // Auto-generate testID if not provided
97
+ const finalTestID =
98
+ testID ||
99
+ (autoTestID && accessibilityLabel
100
+ ? `${testIDPrefix}${testIDPrefix ? "-" : ""}${accessibilityLabel
101
+ .toLowerCase()
102
+ .replace(/\s+/g, "-")
103
+ .replace(/[^a-z0-9-]/g, "")}`
104
+ : undefined);
105
+
106
+ return (
107
+ <WrappedComponent
108
+ {...(restProps as P)}
109
+ ref={ref}
110
+ accessibilityLabel={accessibilityLabel}
111
+ accessibilityRole={accessibilityRole}
112
+ accessibilityHint={accessibilityHint}
113
+ testID={finalTestID}
114
+ accessible={true}
115
+ />
116
+ );
117
+ });
118
+
119
+ EnhancedComponent.displayName = `WithAccessibility(${WrappedComponent.displayName || WrappedComponent.name || "Component"})`;
120
+
121
+ return EnhancedComponent;
122
+ }
123
+
124
+ /**
125
+ * Hook to validate accessibility props at runtime
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * function MyComponent({ accessibilityLabel, ...props }) {
130
+ * useAccessibilityValidation({ accessibilityLabel }, 'MyComponent');
131
+ * return <View {...props} />;
132
+ * }
133
+ * ```
134
+ */
135
+ export function useAccessibilityValidation(
136
+ props: Partial<AccessibilityProps>,
137
+ componentName: string
138
+ ): void {
139
+ if (__DEV__) {
140
+ if (!props.accessibilityLabel && !props.accessibilityLabelledBy) {
141
+ console.warn(
142
+ `[A11y] ${componentName}: Missing accessibilityLabel or accessibilityLabelledBy`
143
+ );
144
+ }
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Create accessible props from a label
150
+ * Utility to quickly generate standard a11y props
151
+ *
152
+ * @example
153
+ * ```tsx
154
+ * <Button {...createA11yProps('Submit form', 'button', 'Saves your changes')} />
155
+ * ```
156
+ */
157
+ export function createA11yProps(
158
+ label: string,
159
+ role?: AccessibilityRole,
160
+ hint?: string
161
+ ): AccessibilityProps & { testID: string } {
162
+ return {
163
+ accessible: true,
164
+ accessibilityLabel: label,
165
+ accessibilityRole: role,
166
+ accessibilityHint: hint,
167
+ testID: label
168
+ .toLowerCase()
169
+ .replace(/\s+/g, "-")
170
+ .replace(/[^a-z0-9-]/g, ""),
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Accessibility context for screen readers
176
+ */
177
+ export const A11yContext = {
178
+ /**
179
+ * Check if accessibility features should be used
180
+ */
181
+ isEnabled: (): boolean => {
182
+ // In a real app, check AccessibilityInfo.isScreenReaderEnabled()
183
+ return true;
184
+ },
185
+
186
+ /**
187
+ * Standard roles for common components
188
+ */
189
+ roles: {
190
+ button: "button" as AccessibilityRole,
191
+ link: "link" as AccessibilityRole,
192
+ image: "image" as AccessibilityRole,
193
+ text: "text" as AccessibilityRole,
194
+ header: "header" as AccessibilityRole,
195
+ search: "search" as AccessibilityRole,
196
+ checkbox: "checkbox" as AccessibilityRole,
197
+ radio: "radio" as AccessibilityRole,
198
+ switch: "switch" as AccessibilityRole,
199
+ slider: "adjustable" as AccessibilityRole,
200
+ tab: "tab" as AccessibilityRole,
201
+ tablist: "tablist" as AccessibilityRole,
202
+ menu: "menu" as AccessibilityRole,
203
+ menuitem: "menuitem" as AccessibilityRole,
204
+ alert: "alert" as AccessibilityRole,
205
+ progressbar: "progressbar" as AccessibilityRole,
206
+ },
207
+ };
208
+
209
+ /**
210
+ * ESLint-like runtime checker for a11y compliance
211
+ * Call this in development to audit component trees
212
+ */
213
+ export function auditAccessibility(
214
+ element: React.ReactElement,
215
+ path: string = "root"
216
+ ): string[] {
217
+ const warnings: string[] = [];
218
+
219
+ if (!element || typeof element !== "object") {
220
+ return warnings;
221
+ }
222
+
223
+ const props = element.props || {};
224
+ const type = element.type;
225
+ const typeName =
226
+ typeof type === "string"
227
+ ? type
228
+ : (type as ComponentType)?.displayName ||
229
+ (type as ComponentType)?.name ||
230
+ "Unknown";
231
+
232
+ // Check for interactive elements without labels
233
+ const interactiveTypes = [
234
+ "Button",
235
+ "Pressable",
236
+ "TouchableOpacity",
237
+ "TouchableHighlight",
238
+ "TouchableWithoutFeedback",
239
+ "Switch",
240
+ "TextInput",
241
+ ];
242
+
243
+ if (
244
+ interactiveTypes.some(
245
+ (t) => typeName.includes(t) || (typeof type === "string" && type === t)
246
+ )
247
+ ) {
248
+ if (!props.accessibilityLabel && !props["aria-label"]) {
249
+ warnings.push(`${path}/${typeName}: Missing accessibility label`);
250
+ }
251
+ }
252
+
253
+ // Check images
254
+ if (typeName === "Image" || typeName === "OptimizedImage") {
255
+ if (!props.accessibilityLabel && !props.alt) {
256
+ warnings.push(`${path}/${typeName}: Image missing alt text/label`);
257
+ }
258
+ }
259
+
260
+ // Recursively check children
261
+ if (props.children) {
262
+ React.Children.forEach(props.children, (child, index) => {
263
+ if (React.isValidElement(child)) {
264
+ warnings.push(
265
+ ...auditAccessibility(child, `${path}/${typeName}[${index}]`)
266
+ );
267
+ }
268
+ });
269
+ }
270
+
271
+ return warnings;
272
+ }