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