@croacroa/react-native-template 2.1.0 → 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 -21
  10. package/README.md +446 -402
  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 -418
  69. package/components/ui/UploadProgress.tsx +189 -0
  70. package/components/ui/VirtualizedList.tsx +288 -285
  71. package/components/ui/index.ts +28 -30
  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 -40
  89. package/hooks/useAnimatedEntry.ts +204 -0
  90. package/hooks/useApi.ts +5 -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 -375
  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 -176
  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,285 +1,288 @@
1
- /**
2
- * @fileoverview High-performance virtualized list component
3
- * Wraps @shopify/flash-list for optimal list rendering with large datasets.
4
- * @module components/ui/VirtualizedList
5
- */
6
-
7
- import React, { useCallback, useMemo } from "react";
8
- import {
9
- View,
10
- Text,
11
- ActivityIndicator,
12
- RefreshControl,
13
- StyleSheet,
14
- } from "react-native";
15
- import { FlashList, FlashListProps } from "@shopify/flash-list";
16
- import { useTheme } from "@/hooks/useTheme";
17
- import { cn } from "@/utils/cn";
18
-
19
- /**
20
- * Props for VirtualizedList component
21
- */
22
- interface VirtualizedListProps<T> extends Omit<FlashListProps<T>, "renderItem" | "estimatedItemSize"> {
23
- /**
24
- * Array of data items to render
25
- */
26
- data: T[];
27
-
28
- /**
29
- * Render function for each item
30
- */
31
- renderItem: (info: { item: T; index: number }) => React.ReactElement | null;
32
-
33
- /**
34
- * Unique key extractor for items
35
- */
36
- keyExtractor: (item: T, index: number) => string;
37
-
38
- /**
39
- * Estimated height of each item for better performance
40
- * Required for optimal FlashList performance
41
- */
42
- estimatedItemSize?: number;
43
-
44
- /**
45
- * Whether the list is currently loading
46
- */
47
- isLoading?: boolean;
48
-
49
- /**
50
- * Whether the list is refreshing (pull-to-refresh)
51
- */
52
- isRefreshing?: boolean;
53
-
54
- /**
55
- * Callback when user pulls to refresh
56
- */
57
- onRefresh?: () => void;
58
-
59
- /**
60
- * Whether there's more data to load
61
- */
62
- hasMore?: boolean;
63
-
64
- /**
65
- * Callback when user scrolls near the end
66
- */
67
- onLoadMore?: () => void;
68
-
69
- /**
70
- * Component to render when list is empty
71
- */
72
- emptyComponent?: React.ReactElement;
73
-
74
- /**
75
- * Message to show when list is empty
76
- */
77
- emptyMessage?: string;
78
-
79
- /**
80
- * Component to render at the bottom when loading more
81
- */
82
- loadingMoreComponent?: React.ReactElement;
83
-
84
- /**
85
- * Additional class name for container
86
- */
87
- className?: string;
88
-
89
- /**
90
- * Threshold for onEndReached (0 to 1)
91
- * @default 0.5
92
- */
93
- onEndReachedThreshold?: number;
94
-
95
- /**
96
- * Whether to show scroll indicator
97
- * @default false
98
- */
99
- showsVerticalScrollIndicator?: boolean;
100
-
101
- /**
102
- * Draw distance for rendering items outside visible area
103
- * Higher values = smoother scroll but more memory
104
- * @default 250
105
- */
106
- drawDistance?: number;
107
- }
108
-
109
- /**
110
- * Default empty component
111
- */
112
- function DefaultEmptyComponent({ message }: { message: string }) {
113
- const { isDark } = useTheme();
114
-
115
- return (
116
- <View className="flex-1 items-center justify-center py-12">
117
- <Text
118
- className={cn(
119
- "text-base",
120
- isDark ? "text-muted-dark" : "text-muted-light"
121
- )}
122
- >
123
- {message}
124
- </Text>
125
- </View>
126
- );
127
- }
128
-
129
- /**
130
- * Default loading more component
131
- */
132
- function DefaultLoadingMoreComponent() {
133
- return (
134
- <View className="items-center justify-center py-4">
135
- <ActivityIndicator size="small" />
136
- </View>
137
- );
138
- }
139
-
140
- /**
141
- * High-performance virtualized list component.
142
- * Optimized for rendering large datasets with minimal memory usage.
143
- *
144
- * Features:
145
- * - Automatic item recycling
146
- * - Pull-to-refresh support
147
- * - Infinite scroll (load more)
148
- * - Empty state handling
149
- * - Loading states
150
- *
151
- * @example
152
- * ```tsx
153
- * interface User {
154
- * id: string;
155
- * name: string;
156
- * }
157
- *
158
- * function UserList() {
159
- * const { data, isLoading, refetch, hasNextPage, fetchNextPage } = useUsers();
160
- *
161
- * return (
162
- * <VirtualizedList<User>
163
- * data={data}
164
- * renderItem={({ item }) => <UserCard user={item} />}
165
- * keyExtractor={(item) => item.id}
166
- * estimatedItemSize={72}
167
- * isLoading={isLoading}
168
- * isRefreshing={isLoading}
169
- * onRefresh={refetch}
170
- * hasMore={hasNextPage}
171
- * onLoadMore={fetchNextPage}
172
- * emptyMessage="No users found"
173
- * />
174
- * );
175
- * }
176
- * ```
177
- */
178
- export function VirtualizedList<T>({
179
- data,
180
- renderItem,
181
- keyExtractor,
182
- estimatedItemSize = 50,
183
- isLoading = false,
184
- isRefreshing = false,
185
- onRefresh,
186
- hasMore = false,
187
- onLoadMore,
188
- emptyComponent,
189
- emptyMessage = "No items found",
190
- loadingMoreComponent,
191
- className,
192
- onEndReachedThreshold = 0.5,
193
- showsVerticalScrollIndicator = false,
194
- drawDistance = 250,
195
- ...flashListProps
196
- }: VirtualizedListProps<T>) {
197
- const { isDark } = useTheme();
198
-
199
- // Memoize empty component
200
- const ListEmptyComponent = useMemo(() => {
201
- if (isLoading) return null;
202
- if (emptyComponent) return emptyComponent;
203
- return <DefaultEmptyComponent message={emptyMessage} />;
204
- }, [isLoading, emptyComponent, emptyMessage]);
205
-
206
- // Memoize footer component
207
- const ListFooterComponent = useCallback(() => {
208
- if (!hasMore || data.length === 0) return null;
209
- if (loadingMoreComponent) return loadingMoreComponent;
210
- return <DefaultLoadingMoreComponent />;
211
- }, [hasMore, data.length, loadingMoreComponent]);
212
-
213
- // Handle end reached
214
- const handleEndReached = useCallback(() => {
215
- if (hasMore && onLoadMore && !isLoading) {
216
- onLoadMore();
217
- }
218
- }, [hasMore, onLoadMore, isLoading]);
219
-
220
- // Memoize refresh control
221
- const refreshControl = useMemo(() => {
222
- if (!onRefresh) return undefined;
223
-
224
- return (
225
- <RefreshControl
226
- refreshing={isRefreshing}
227
- onRefresh={onRefresh}
228
- tintColor={isDark ? "#94a3b8" : "#64748b"}
229
- colors={["#3b82f6"]}
230
- />
231
- );
232
- }, [onRefresh, isRefreshing, isDark]);
233
-
234
- // Loading state
235
- if (isLoading && data.length === 0) {
236
- return (
237
- <View className={cn("flex-1 items-center justify-center", className)}>
238
- <ActivityIndicator size="large" color="#3b82f6" />
239
- </View>
240
- );
241
- }
242
-
243
- return (
244
- <FlashList
245
- data={data}
246
- renderItem={renderItem}
247
- keyExtractor={keyExtractor}
248
- ListEmptyComponent={ListEmptyComponent}
249
- ListFooterComponent={ListFooterComponent}
250
- refreshControl={refreshControl}
251
- onEndReached={handleEndReached}
252
- onEndReachedThreshold={onEndReachedThreshold}
253
- showsVerticalScrollIndicator={showsVerticalScrollIndicator}
254
- // FlashList specific optimizations
255
- estimatedItemSize={estimatedItemSize}
256
- drawDistance={drawDistance}
257
- {...flashListProps}
258
- contentContainerStyle={[
259
- styles.container,
260
- flashListProps.contentContainerStyle,
261
- ]}
262
- />
263
- );
264
- }
265
-
266
- const styles = StyleSheet.create({
267
- container: {
268
- flexGrow: 1,
269
- },
270
- });
271
-
272
- /**
273
- * Horizontal virtualized list variant
274
- */
275
- export function HorizontalVirtualizedList<T>(
276
- props: Omit<VirtualizedListProps<T>, "horizontal">
277
- ) {
278
- return (
279
- <VirtualizedList
280
- {...props}
281
- horizontal
282
- showsHorizontalScrollIndicator={false}
283
- />
284
- );
285
- }
1
+ /**
2
+ * @fileoverview High-performance virtualized list component
3
+ * Wraps @shopify/flash-list for optimal list rendering with large datasets.
4
+ * @module components/ui/VirtualizedList
5
+ */
6
+
7
+ import React, { useCallback, useMemo } from "react";
8
+ import {
9
+ View,
10
+ Text,
11
+ ActivityIndicator,
12
+ RefreshControl,
13
+ StyleSheet,
14
+ } from "react-native";
15
+ import { FlashList, FlashListProps } from "@shopify/flash-list";
16
+ import { useTheme } from "@/hooks/useTheme";
17
+ import { cn } from "@/utils/cn";
18
+
19
+ /**
20
+ * Props for VirtualizedList component
21
+ */
22
+ interface VirtualizedListProps<T> extends Omit<
23
+ FlashListProps<T>,
24
+ "renderItem" | "estimatedItemSize"
25
+ > {
26
+ /**
27
+ * Array of data items to render
28
+ */
29
+ data: T[];
30
+
31
+ /**
32
+ * Render function for each item
33
+ */
34
+ renderItem: (info: { item: T; index: number }) => React.ReactElement | null;
35
+
36
+ /**
37
+ * Unique key extractor for items
38
+ */
39
+ keyExtractor: (item: T, index: number) => string;
40
+
41
+ /**
42
+ * Estimated height of each item for better performance
43
+ * Required for optimal FlashList performance
44
+ */
45
+ estimatedItemSize?: number;
46
+
47
+ /**
48
+ * Whether the list is currently loading
49
+ */
50
+ isLoading?: boolean;
51
+
52
+ /**
53
+ * Whether the list is refreshing (pull-to-refresh)
54
+ */
55
+ isRefreshing?: boolean;
56
+
57
+ /**
58
+ * Callback when user pulls to refresh
59
+ */
60
+ onRefresh?: () => void;
61
+
62
+ /**
63
+ * Whether there's more data to load
64
+ */
65
+ hasMore?: boolean;
66
+
67
+ /**
68
+ * Callback when user scrolls near the end
69
+ */
70
+ onLoadMore?: () => void;
71
+
72
+ /**
73
+ * Component to render when list is empty
74
+ */
75
+ emptyComponent?: React.ReactElement;
76
+
77
+ /**
78
+ * Message to show when list is empty
79
+ */
80
+ emptyMessage?: string;
81
+
82
+ /**
83
+ * Component to render at the bottom when loading more
84
+ */
85
+ loadingMoreComponent?: React.ReactElement;
86
+
87
+ /**
88
+ * Additional class name for container
89
+ */
90
+ className?: string;
91
+
92
+ /**
93
+ * Threshold for onEndReached (0 to 1)
94
+ * @default 0.5
95
+ */
96
+ onEndReachedThreshold?: number;
97
+
98
+ /**
99
+ * Whether to show scroll indicator
100
+ * @default false
101
+ */
102
+ showsVerticalScrollIndicator?: boolean;
103
+
104
+ /**
105
+ * Draw distance for rendering items outside visible area
106
+ * Higher values = smoother scroll but more memory
107
+ * @default 250
108
+ */
109
+ drawDistance?: number;
110
+ }
111
+
112
+ /**
113
+ * Default empty component
114
+ */
115
+ function DefaultEmptyComponent({ message }: { message: string }) {
116
+ const { isDark } = useTheme();
117
+
118
+ return (
119
+ <View className="flex-1 items-center justify-center py-12">
120
+ <Text
121
+ className={cn(
122
+ "text-base",
123
+ isDark ? "text-muted-dark" : "text-muted-light"
124
+ )}
125
+ >
126
+ {message}
127
+ </Text>
128
+ </View>
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Default loading more component
134
+ */
135
+ function DefaultLoadingMoreComponent() {
136
+ return (
137
+ <View className="items-center justify-center py-4">
138
+ <ActivityIndicator size="small" />
139
+ </View>
140
+ );
141
+ }
142
+
143
+ /**
144
+ * High-performance virtualized list component.
145
+ * Optimized for rendering large datasets with minimal memory usage.
146
+ *
147
+ * Features:
148
+ * - Automatic item recycling
149
+ * - Pull-to-refresh support
150
+ * - Infinite scroll (load more)
151
+ * - Empty state handling
152
+ * - Loading states
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * interface User {
157
+ * id: string;
158
+ * name: string;
159
+ * }
160
+ *
161
+ * function UserList() {
162
+ * const { data, isLoading, refetch, hasNextPage, fetchNextPage } = useUsers();
163
+ *
164
+ * return (
165
+ * <VirtualizedList<User>
166
+ * data={data}
167
+ * renderItem={({ item }) => <UserCard user={item} />}
168
+ * keyExtractor={(item) => item.id}
169
+ * estimatedItemSize={72}
170
+ * isLoading={isLoading}
171
+ * isRefreshing={isLoading}
172
+ * onRefresh={refetch}
173
+ * hasMore={hasNextPage}
174
+ * onLoadMore={fetchNextPage}
175
+ * emptyMessage="No users found"
176
+ * />
177
+ * );
178
+ * }
179
+ * ```
180
+ */
181
+ export function VirtualizedList<T>({
182
+ data,
183
+ renderItem,
184
+ keyExtractor,
185
+ estimatedItemSize = 50,
186
+ isLoading = false,
187
+ isRefreshing = false,
188
+ onRefresh,
189
+ hasMore = false,
190
+ onLoadMore,
191
+ emptyComponent,
192
+ emptyMessage = "No items found",
193
+ loadingMoreComponent,
194
+ className,
195
+ onEndReachedThreshold = 0.5,
196
+ showsVerticalScrollIndicator = false,
197
+ drawDistance = 250,
198
+ ...flashListProps
199
+ }: VirtualizedListProps<T>) {
200
+ const { isDark } = useTheme();
201
+
202
+ // Memoize empty component
203
+ const ListEmptyComponent = useMemo(() => {
204
+ if (isLoading) return null;
205
+ if (emptyComponent) return emptyComponent;
206
+ return <DefaultEmptyComponent message={emptyMessage} />;
207
+ }, [isLoading, emptyComponent, emptyMessage]);
208
+
209
+ // Memoize footer component
210
+ const ListFooterComponent = useCallback(() => {
211
+ if (!hasMore || data.length === 0) return null;
212
+ if (loadingMoreComponent) return loadingMoreComponent;
213
+ return <DefaultLoadingMoreComponent />;
214
+ }, [hasMore, data.length, loadingMoreComponent]);
215
+
216
+ // Handle end reached
217
+ const handleEndReached = useCallback(() => {
218
+ if (hasMore && onLoadMore && !isLoading) {
219
+ onLoadMore();
220
+ }
221
+ }, [hasMore, onLoadMore, isLoading]);
222
+
223
+ // Memoize refresh control
224
+ const refreshControl = useMemo(() => {
225
+ if (!onRefresh) return undefined;
226
+
227
+ return (
228
+ <RefreshControl
229
+ refreshing={isRefreshing}
230
+ onRefresh={onRefresh}
231
+ tintColor={isDark ? "#94a3b8" : "#64748b"}
232
+ colors={["#3b82f6"]}
233
+ />
234
+ );
235
+ }, [onRefresh, isRefreshing, isDark]);
236
+
237
+ // Loading state
238
+ if (isLoading && data.length === 0) {
239
+ return (
240
+ <View className={cn("flex-1 items-center justify-center", className)}>
241
+ <ActivityIndicator size="large" color="#3b82f6" />
242
+ </View>
243
+ );
244
+ }
245
+
246
+ return (
247
+ <FlashList
248
+ data={data}
249
+ renderItem={renderItem}
250
+ keyExtractor={keyExtractor}
251
+ ListEmptyComponent={ListEmptyComponent}
252
+ ListFooterComponent={ListFooterComponent}
253
+ refreshControl={refreshControl}
254
+ onEndReached={handleEndReached}
255
+ onEndReachedThreshold={onEndReachedThreshold}
256
+ showsVerticalScrollIndicator={showsVerticalScrollIndicator}
257
+ // FlashList specific optimizations
258
+ estimatedItemSize={estimatedItemSize}
259
+ drawDistance={drawDistance}
260
+ {...flashListProps}
261
+ contentContainerStyle={[
262
+ styles.container,
263
+ flashListProps.contentContainerStyle,
264
+ ]}
265
+ />
266
+ );
267
+ }
268
+
269
+ const styles = StyleSheet.create({
270
+ container: {
271
+ flexGrow: 1,
272
+ },
273
+ });
274
+
275
+ /**
276
+ * Horizontal virtualized list variant
277
+ */
278
+ export function HorizontalVirtualizedList<T>(
279
+ props: Omit<VirtualizedListProps<T>, "horizontal">
280
+ ) {
281
+ return (
282
+ <VirtualizedList
283
+ {...props}
284
+ horizontal
285
+ showsHorizontalScrollIndicator={false}
286
+ />
287
+ );
288
+ }
@@ -1,30 +1,28 @@
1
- export { Button } from "./Button";
2
- export { Input } from "./Input";
3
- export { Card } from "./Card";
4
- export { Modal } from "./Modal";
5
- export { AnimatedButton } from "./AnimatedButton";
6
- export { AnimatedCard, AnimatedList } from "./AnimatedCard";
7
- export { Select } from "./Select";
8
- export type { SelectOption } from "./Select";
9
- export { Checkbox, CheckboxGroup } from "./Checkbox";
10
- export { BottomSheet, useBottomSheet } from "./BottomSheet";
11
- export type { BottomSheetRef } from "./BottomSheet";
12
- export { Avatar, AvatarGroup } from "./Avatar";
13
- export { Badge, Chip, CountBadge } from "./Badge";
14
- export {
15
- OptimizedImage,
16
- BackgroundImage,
17
- ProgressiveImage,
18
- } from "./OptimizedImage";
19
- export { Skeleton, SkeletonText, SkeletonCircle } from "./Skeleton";
20
- export {
21
- VirtualizedList,
22
- HorizontalVirtualizedList,
23
- } from "./VirtualizedList";
24
- export {
25
- ToastProvider,
26
- useToast,
27
- toastManager,
28
- setToastRef,
29
- } from "./Toast";
30
- export type { ToastConfig, ToastType } from "./Toast";
1
+ export { Button } from "./Button";
2
+ export { Input } from "./Input";
3
+ export { Card } from "./Card";
4
+ export { Modal } from "./Modal";
5
+ export { AnimatedButton } from "./AnimatedButton";
6
+ export { AnimatedCard, AnimatedList } from "./AnimatedCard";
7
+ export { Select } from "./Select";
8
+ export type { SelectOption } from "./Select";
9
+ export { Checkbox, CheckboxGroup } from "./Checkbox";
10
+ export { BottomSheet, useBottomSheet } from "./BottomSheet";
11
+ export type { BottomSheetRef } from "./BottomSheet";
12
+ export { Avatar, AvatarGroup } from "./Avatar";
13
+ export { Badge, Chip, CountBadge } from "./Badge";
14
+ export {
15
+ OptimizedImage,
16
+ BackgroundImage,
17
+ ProgressiveImage,
18
+ } from "./OptimizedImage";
19
+ export { Skeleton, SkeletonText, SkeletonCircle } from "./Skeleton";
20
+ export { VirtualizedList, HorizontalVirtualizedList } from "./VirtualizedList";
21
+ export { ToastProvider, useToast, toastManager, setToastRef } from "./Toast";
22
+ export type { ToastConfig, ToastType } from "./Toast";
23
+ export { AnimatedScreen } from "./AnimatedScreen";
24
+ export { AnimatedListItem } from "./AnimatedList";
25
+ export { ImagePickerButton } from "./ImagePickerButton";
26
+ export { UploadProgress } from "./UploadProgress";
27
+ export { ForceUpdateScreen } from "./ForceUpdateScreen";
28
+ export { FeatureGate } from "./FeatureGate";