@croacroa/react-native-template 2.0.1 → 3.2.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 (172) hide show
  1. package/.env.example +5 -0
  2. package/.eslintrc.js +8 -0
  3. package/.github/workflows/ci.yml +187 -187
  4. package/.github/workflows/eas-build.yml +55 -55
  5. package/.github/workflows/eas-update.yml +50 -50
  6. package/.github/workflows/npm-publish.yml +57 -0
  7. package/CHANGELOG.md +195 -106
  8. package/CONTRIBUTING.md +377 -377
  9. package/LICENSE +21 -0
  10. package/README.md +446 -399
  11. package/__tests__/accessibility/components.test.tsx +285 -0
  12. package/__tests__/components/Button.test.tsx +2 -4
  13. package/__tests__/components/__snapshots__/snapshots.test.tsx.snap +512 -0
  14. package/__tests__/components/snapshots.test.tsx +131 -131
  15. package/__tests__/helpers/a11y.ts +54 -0
  16. package/__tests__/hooks/useAnalytics.test.ts +100 -0
  17. package/__tests__/hooks/useAnimations.test.ts +70 -0
  18. package/__tests__/hooks/useAuth.test.tsx +71 -28
  19. package/__tests__/hooks/useMedia.test.ts +318 -0
  20. package/__tests__/hooks/usePayments.test.tsx +307 -0
  21. package/__tests__/hooks/usePermission.test.ts +230 -0
  22. package/__tests__/hooks/useWebSocket.test.ts +329 -0
  23. package/__tests__/integration/auth-api.test.tsx +224 -227
  24. package/__tests__/performance/VirtualizedList.perf.test.tsx +385 -362
  25. package/__tests__/services/api.test.ts +24 -6
  26. package/app/(auth)/home.tsx +11 -9
  27. package/app/(auth)/profile.tsx +8 -6
  28. package/app/(auth)/settings.tsx +11 -9
  29. package/app/(public)/forgot-password.tsx +25 -15
  30. package/app/(public)/login.tsx +48 -12
  31. package/app/(public)/onboarding.tsx +5 -5
  32. package/app/(public)/register.tsx +24 -15
  33. package/app/_layout.tsx +6 -3
  34. package/app.config.ts +27 -2
  35. package/assets/images/.gitkeep +7 -7
  36. package/assets/images/adaptive-icon.png +0 -0
  37. package/assets/images/favicon.png +0 -0
  38. package/assets/images/icon.png +0 -0
  39. package/assets/images/notification-icon.png +0 -0
  40. package/assets/images/splash.png +0 -0
  41. package/components/ErrorBoundary.tsx +73 -28
  42. package/components/auth/SocialLoginButtons.tsx +168 -0
  43. package/components/forms/FormInput.tsx +5 -3
  44. package/components/onboarding/OnboardingScreen.tsx +370 -370
  45. package/components/onboarding/index.ts +2 -2
  46. package/components/providers/AnalyticsProvider.tsx +67 -0
  47. package/components/providers/SuspenseBoundary.tsx +359 -357
  48. package/components/providers/index.ts +24 -21
  49. package/components/ui/AnimatedButton.tsx +1 -9
  50. package/components/ui/AnimatedList.tsx +98 -0
  51. package/components/ui/AnimatedScreen.tsx +89 -0
  52. package/components/ui/Avatar.tsx +319 -316
  53. package/components/ui/Badge.tsx +416 -416
  54. package/components/ui/BottomSheet.tsx +307 -307
  55. package/components/ui/Button.tsx +11 -3
  56. package/components/ui/Checkbox.tsx +261 -261
  57. package/components/ui/FeatureGate.tsx +57 -0
  58. package/components/ui/ForceUpdateScreen.tsx +108 -0
  59. package/components/ui/ImagePickerButton.tsx +180 -0
  60. package/components/ui/Input.stories.tsx +2 -10
  61. package/components/ui/Input.tsx +2 -10
  62. package/components/ui/OptimizedImage.tsx +369 -369
  63. package/components/ui/Paywall.tsx +253 -0
  64. package/components/ui/PermissionGate.tsx +155 -0
  65. package/components/ui/PurchaseButton.tsx +84 -0
  66. package/components/ui/Select.tsx +240 -240
  67. package/components/ui/Skeleton.tsx +3 -1
  68. package/components/ui/Toast.tsx +427 -0
  69. package/components/ui/UploadProgress.tsx +189 -0
  70. package/components/ui/VirtualizedList.tsx +288 -285
  71. package/components/ui/index.ts +28 -23
  72. package/constants/config.ts +135 -97
  73. package/docs/adr/001-state-management.md +79 -79
  74. package/docs/adr/002-styling-approach.md +130 -130
  75. package/docs/adr/003-data-fetching.md +155 -155
  76. package/docs/adr/004-auth-adapter-pattern.md +144 -144
  77. package/docs/adr/README.md +78 -78
  78. package/docs/guides/analytics-posthog.md +121 -0
  79. package/docs/guides/auth-supabase.md +162 -0
  80. package/docs/guides/feature-flags-launchdarkly.md +150 -0
  81. package/docs/guides/payments-revenuecat.md +169 -0
  82. package/docs/plans/2026-02-22-phase6-implementation.md +3222 -0
  83. package/docs/plans/2026-02-22-phase6-template-completion-design.md +196 -0
  84. package/docs/plans/2026-02-23-npm-publish-design.md +31 -0
  85. package/docs/plans/2026-02-23-phase7-polish-documentation-design.md +79 -0
  86. package/docs/plans/2026-02-23-phase8-additional-features-design.md +136 -0
  87. package/eas.json +2 -1
  88. package/hooks/index.ts +70 -27
  89. package/hooks/useAnimatedEntry.ts +204 -0
  90. package/hooks/useApi.ts +64 -4
  91. package/hooks/useAuth.tsx +7 -3
  92. package/hooks/useBiometrics.ts +295 -295
  93. package/hooks/useChannel.ts +111 -0
  94. package/hooks/useDeepLinking.ts +256 -256
  95. package/hooks/useExperiment.ts +36 -0
  96. package/hooks/useFeatureFlag.ts +59 -0
  97. package/hooks/useForceUpdate.ts +91 -0
  98. package/hooks/useImagePicker.ts +281 -0
  99. package/hooks/useInAppReview.ts +64 -0
  100. package/hooks/useMFA.ts +509 -499
  101. package/hooks/useParallax.ts +142 -0
  102. package/hooks/usePerformance.ts +434 -434
  103. package/hooks/usePermission.ts +190 -0
  104. package/hooks/usePresence.ts +129 -0
  105. package/hooks/useProducts.ts +36 -0
  106. package/hooks/usePurchase.ts +103 -0
  107. package/hooks/useRateLimit.ts +70 -0
  108. package/hooks/useSubscription.ts +49 -0
  109. package/hooks/useTrackEvent.ts +52 -0
  110. package/hooks/useTrackScreen.ts +40 -0
  111. package/hooks/useUpdates.ts +358 -358
  112. package/hooks/useUpload.ts +165 -0
  113. package/hooks/useWebSocket.ts +111 -0
  114. package/i18n/index.ts +197 -194
  115. package/i18n/locales/ar.json +170 -101
  116. package/i18n/locales/de.json +170 -101
  117. package/i18n/locales/en.json +170 -101
  118. package/i18n/locales/es.json +170 -101
  119. package/i18n/locales/fr.json +170 -101
  120. package/jest.config.js +1 -1
  121. package/maestro/README.md +113 -113
  122. package/maestro/config.yaml +35 -35
  123. package/maestro/flows/login.yaml +62 -62
  124. package/maestro/flows/mfa-login.yaml +92 -92
  125. package/maestro/flows/mfa-setup.yaml +86 -86
  126. package/maestro/flows/navigation.yaml +68 -68
  127. package/maestro/flows/offline-conflict.yaml +101 -101
  128. package/maestro/flows/offline-sync.yaml +128 -128
  129. package/maestro/flows/offline.yaml +60 -60
  130. package/maestro/flows/register.yaml +94 -94
  131. package/package.json +188 -175
  132. package/scripts/generate-placeholders.js +38 -0
  133. package/services/analytics/adapters/console.ts +50 -0
  134. package/services/analytics/analytics-adapter.ts +94 -0
  135. package/services/analytics/types.ts +73 -0
  136. package/services/analytics.ts +428 -428
  137. package/services/api.ts +419 -340
  138. package/services/auth/social/apple.ts +110 -0
  139. package/services/auth/social/google.ts +159 -0
  140. package/services/auth/social/social-auth.ts +100 -0
  141. package/services/auth/social/types.ts +80 -0
  142. package/services/authAdapter.ts +333 -333
  143. package/services/backgroundSync.ts +652 -626
  144. package/services/feature-flags/adapters/mock.ts +108 -0
  145. package/services/feature-flags/feature-flag-adapter.ts +174 -0
  146. package/services/feature-flags/types.ts +79 -0
  147. package/services/force-update.ts +140 -0
  148. package/services/index.ts +116 -54
  149. package/services/media/compression.ts +91 -0
  150. package/services/media/media-picker.ts +151 -0
  151. package/services/media/media-upload.ts +160 -0
  152. package/services/payments/adapters/mock.ts +159 -0
  153. package/services/payments/payment-adapter.ts +118 -0
  154. package/services/payments/types.ts +131 -0
  155. package/services/permissions/permission-manager.ts +284 -0
  156. package/services/permissions/types.ts +104 -0
  157. package/services/realtime/types.ts +100 -0
  158. package/services/realtime/websocket-manager.ts +441 -0
  159. package/services/security.ts +289 -286
  160. package/services/sentry.ts +4 -4
  161. package/stores/appStore.ts +9 -0
  162. package/stores/notificationStore.ts +3 -1
  163. package/tailwind.config.js +47 -47
  164. package/tsconfig.json +37 -13
  165. package/types/user.ts +1 -1
  166. package/utils/accessibility.ts +446 -446
  167. package/utils/animations/presets.ts +182 -0
  168. package/utils/animations/transitions.ts +62 -0
  169. package/utils/index.ts +63 -52
  170. package/utils/toast.ts +9 -2
  171. package/utils/validation.ts +4 -1
  172. package/utils/withAccessibility.tsx +272 -272
@@ -1,272 +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
- }
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
+ }