@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,307 +1,307 @@
1
- import { forwardRef, useCallback, useMemo, ReactNode } from "react";
2
- import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
3
- import GorhomBottomSheet, {
4
- BottomSheetBackdrop,
5
- BottomSheetView,
6
- BottomSheetScrollView,
7
- BottomSheetBackdropProps,
8
- } from "@gorhom/bottom-sheet";
9
- import { Ionicons } from "@expo/vector-icons";
10
- import { useTheme } from "@/hooks/useTheme";
11
- import { cn } from "@/utils/cn";
12
-
13
- /**
14
- * Hook to control BottomSheet imperatively
15
- *
16
- * @example
17
- * ```tsx
18
- * function MyComponent() {
19
- * const { ref, open, close, snapTo } = useBottomSheet();
20
- *
21
- * return (
22
- * <>
23
- * <Button onPress={() => open()}>Open Sheet</Button>
24
- * <BottomSheet ref={ref}>
25
- * <Text>Content</Text>
26
- * </BottomSheet>
27
- * </>
28
- * );
29
- * }
30
- * ```
31
- */
32
- import { useRef } from "react";
33
-
34
- interface BottomSheetProps {
35
- /**
36
- * Content to render inside the bottom sheet
37
- */
38
- children: ReactNode;
39
-
40
- /**
41
- * Snap points for the bottom sheet (e.g., ['25%', '50%', '90%'])
42
- */
43
- snapPoints?: (string | number)[];
44
-
45
- /**
46
- * Initial snap point index
47
- */
48
- index?: number;
49
-
50
- /**
51
- * Title displayed in the header
52
- */
53
- title?: string;
54
-
55
- /**
56
- * Show close button in header
57
- */
58
- showCloseButton?: boolean;
59
-
60
- /**
61
- * Called when the sheet is closed
62
- */
63
- onClose?: () => void;
64
-
65
- /**
66
- * Called when the sheet index changes
67
- */
68
- onChange?: (index: number) => void;
69
-
70
- /**
71
- * Whether to enable backdrop
72
- */
73
- enableBackdrop?: boolean;
74
-
75
- /**
76
- * Whether to close on backdrop press
77
- */
78
- closeOnBackdropPress?: boolean;
79
-
80
- /**
81
- * Whether content is scrollable
82
- */
83
- scrollable?: boolean;
84
-
85
- /**
86
- * Whether to enable handle
87
- */
88
- enableHandle?: boolean;
89
-
90
- /**
91
- * Additional styles for the container
92
- */
93
- containerClassName?: string;
94
-
95
- /**
96
- * Additional styles for the content
97
- */
98
- contentClassName?: string;
99
- }
100
-
101
- export type BottomSheetRef = GorhomBottomSheet;
102
-
103
- export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
104
- (
105
- {
106
- children,
107
- snapPoints: customSnapPoints,
108
- index = -1,
109
- title,
110
- showCloseButton = true,
111
- onClose,
112
- onChange,
113
- enableBackdrop = true,
114
- closeOnBackdropPress = true,
115
- scrollable = false,
116
- enableHandle = true,
117
- containerClassName,
118
- contentClassName,
119
- },
120
- ref
121
- ) => {
122
- const { isDark } = useTheme();
123
-
124
- // Default snap points
125
- const snapPoints = useMemo(
126
- () => customSnapPoints || ["50%", "90%"],
127
- [customSnapPoints]
128
- );
129
-
130
- // Handle sheet changes
131
- const handleSheetChanges = useCallback(
132
- (sheetIndex: number) => {
133
- onChange?.(sheetIndex);
134
- if (sheetIndex === -1) {
135
- onClose?.();
136
- }
137
- },
138
- [onChange, onClose]
139
- );
140
-
141
- // Handle close button press
142
- const handleClose = useCallback(() => {
143
- if (ref && "current" in ref && ref.current) {
144
- ref.current.close();
145
- }
146
- onClose?.();
147
- }, [ref, onClose]);
148
-
149
- // Backdrop component
150
- const renderBackdrop = useCallback(
151
- (props: BottomSheetBackdropProps) => (
152
- <BottomSheetBackdrop
153
- {...props}
154
- disappearsOnIndex={-1}
155
- appearsOnIndex={0}
156
- pressBehavior={closeOnBackdropPress ? "close" : "none"}
157
- opacity={0.5}
158
- />
159
- ),
160
- [closeOnBackdropPress]
161
- );
162
-
163
- // Handle component
164
- const renderHandle = useCallback(() => {
165
- if (!enableHandle) return null;
166
-
167
- return (
168
- <View className="items-center pt-2 pb-1">
169
- <View
170
- className={cn(
171
- "w-10 h-1 rounded-full",
172
- isDark ? "bg-gray-600" : "bg-gray-300"
173
- )}
174
- />
175
- </View>
176
- );
177
- }, [enableHandle, isDark]);
178
-
179
- const ContentWrapper = scrollable ? BottomSheetScrollView : BottomSheetView;
180
-
181
- return (
182
- <GorhomBottomSheet
183
- ref={ref}
184
- index={index}
185
- snapPoints={snapPoints}
186
- onChange={handleSheetChanges}
187
- backdropComponent={enableBackdrop ? renderBackdrop : null}
188
- handleComponent={renderHandle}
189
- enablePanDownToClose
190
- backgroundStyle={[
191
- styles.background,
192
- { backgroundColor: isDark ? "#1e293b" : "#ffffff" },
193
- ]}
194
- style={styles.sheet}
195
- >
196
- <View
197
- className={cn(
198
- "flex-1",
199
- isDark ? "bg-surface-dark" : "bg-white",
200
- containerClassName
201
- )}
202
- >
203
- {/* Header */}
204
- {(title || showCloseButton) && (
205
- <View
206
- className={cn(
207
- "flex-row items-center justify-between px-4 py-3 border-b",
208
- isDark ? "border-gray-700" : "border-gray-100"
209
- )}
210
- >
211
- <Text
212
- className={cn(
213
- "text-lg font-semibold",
214
- isDark ? "text-text-dark" : "text-text-light"
215
- )}
216
- >
217
- {title || ""}
218
- </Text>
219
- {showCloseButton && (
220
- <TouchableOpacity
221
- onPress={handleClose}
222
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
223
- className="p-1"
224
- >
225
- <Ionicons
226
- name="close"
227
- size={24}
228
- color={isDark ? "#f8fafc" : "#0f172a"}
229
- />
230
- </TouchableOpacity>
231
- )}
232
- </View>
233
- )}
234
-
235
- {/* Content */}
236
- <ContentWrapper
237
- style={styles.contentContainer}
238
- contentContainerStyle={[
239
- styles.content,
240
- scrollable && styles.scrollContent,
241
- ]}
242
- >
243
- <View className={cn("flex-1", contentClassName)}>{children}</View>
244
- </ContentWrapper>
245
- </View>
246
- </GorhomBottomSheet>
247
- );
248
- }
249
- );
250
-
251
- BottomSheet.displayName = "BottomSheet";
252
-
253
- const styles = StyleSheet.create({
254
- sheet: {
255
- shadowColor: "#000",
256
- shadowOffset: { width: 0, height: -4 },
257
- shadowOpacity: 0.1,
258
- shadowRadius: 8,
259
- elevation: 5,
260
- },
261
- background: {
262
- borderTopLeftRadius: 24,
263
- borderTopRightRadius: 24,
264
- },
265
- contentContainer: {
266
- flex: 1,
267
- },
268
- content: {
269
- flex: 1,
270
- },
271
- scrollContent: {
272
- paddingBottom: 40,
273
- },
274
- });
275
-
276
- export function useBottomSheet() {
277
- const ref = useRef<BottomSheetRef>(null);
278
-
279
- const open = useCallback((snapIndex = 0) => {
280
- ref.current?.snapToIndex(snapIndex);
281
- }, []);
282
-
283
- const close = useCallback(() => {
284
- ref.current?.close();
285
- }, []);
286
-
287
- const snapTo = useCallback((index: number) => {
288
- ref.current?.snapToIndex(index);
289
- }, []);
290
-
291
- const expand = useCallback(() => {
292
- ref.current?.expand();
293
- }, []);
294
-
295
- const collapse = useCallback(() => {
296
- ref.current?.collapse();
297
- }, []);
298
-
299
- return {
300
- ref,
301
- open,
302
- close,
303
- snapTo,
304
- expand,
305
- collapse,
306
- };
307
- }
1
+ import { forwardRef, useCallback, useMemo, ReactNode } from "react";
2
+ import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
3
+ import GorhomBottomSheet, {
4
+ BottomSheetBackdrop,
5
+ BottomSheetView,
6
+ BottomSheetScrollView,
7
+ BottomSheetBackdropProps,
8
+ } from "@gorhom/bottom-sheet";
9
+ import { Ionicons } from "@expo/vector-icons";
10
+ import { useTheme } from "@/hooks/useTheme";
11
+ import { cn } from "@/utils/cn";
12
+
13
+ /**
14
+ * Hook to control BottomSheet imperatively
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * function MyComponent() {
19
+ * const { ref, open, close, snapTo } = useBottomSheet();
20
+ *
21
+ * return (
22
+ * <>
23
+ * <Button onPress={() => open()}>Open Sheet</Button>
24
+ * <BottomSheet ref={ref}>
25
+ * <Text>Content</Text>
26
+ * </BottomSheet>
27
+ * </>
28
+ * );
29
+ * }
30
+ * ```
31
+ */
32
+ import { useRef } from "react";
33
+
34
+ interface BottomSheetProps {
35
+ /**
36
+ * Content to render inside the bottom sheet
37
+ */
38
+ children: ReactNode;
39
+
40
+ /**
41
+ * Snap points for the bottom sheet (e.g., ['25%', '50%', '90%'])
42
+ */
43
+ snapPoints?: (string | number)[];
44
+
45
+ /**
46
+ * Initial snap point index
47
+ */
48
+ index?: number;
49
+
50
+ /**
51
+ * Title displayed in the header
52
+ */
53
+ title?: string;
54
+
55
+ /**
56
+ * Show close button in header
57
+ */
58
+ showCloseButton?: boolean;
59
+
60
+ /**
61
+ * Called when the sheet is closed
62
+ */
63
+ onClose?: () => void;
64
+
65
+ /**
66
+ * Called when the sheet index changes
67
+ */
68
+ onChange?: (index: number) => void;
69
+
70
+ /**
71
+ * Whether to enable backdrop
72
+ */
73
+ enableBackdrop?: boolean;
74
+
75
+ /**
76
+ * Whether to close on backdrop press
77
+ */
78
+ closeOnBackdropPress?: boolean;
79
+
80
+ /**
81
+ * Whether content is scrollable
82
+ */
83
+ scrollable?: boolean;
84
+
85
+ /**
86
+ * Whether to enable handle
87
+ */
88
+ enableHandle?: boolean;
89
+
90
+ /**
91
+ * Additional styles for the container
92
+ */
93
+ containerClassName?: string;
94
+
95
+ /**
96
+ * Additional styles for the content
97
+ */
98
+ contentClassName?: string;
99
+ }
100
+
101
+ export type BottomSheetRef = GorhomBottomSheet;
102
+
103
+ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>(
104
+ (
105
+ {
106
+ children,
107
+ snapPoints: customSnapPoints,
108
+ index = -1,
109
+ title,
110
+ showCloseButton = true,
111
+ onClose,
112
+ onChange,
113
+ enableBackdrop = true,
114
+ closeOnBackdropPress = true,
115
+ scrollable = false,
116
+ enableHandle = true,
117
+ containerClassName,
118
+ contentClassName,
119
+ },
120
+ ref
121
+ ) => {
122
+ const { isDark } = useTheme();
123
+
124
+ // Default snap points
125
+ const snapPoints = useMemo(
126
+ () => customSnapPoints || ["50%", "90%"],
127
+ [customSnapPoints]
128
+ );
129
+
130
+ // Handle sheet changes
131
+ const handleSheetChanges = useCallback(
132
+ (sheetIndex: number) => {
133
+ onChange?.(sheetIndex);
134
+ if (sheetIndex === -1) {
135
+ onClose?.();
136
+ }
137
+ },
138
+ [onChange, onClose]
139
+ );
140
+
141
+ // Handle close button press
142
+ const handleClose = useCallback(() => {
143
+ if (ref && "current" in ref && ref.current) {
144
+ ref.current.close();
145
+ }
146
+ onClose?.();
147
+ }, [ref, onClose]);
148
+
149
+ // Backdrop component
150
+ const renderBackdrop = useCallback(
151
+ (props: BottomSheetBackdropProps) => (
152
+ <BottomSheetBackdrop
153
+ {...props}
154
+ disappearsOnIndex={-1}
155
+ appearsOnIndex={0}
156
+ pressBehavior={closeOnBackdropPress ? "close" : "none"}
157
+ opacity={0.5}
158
+ />
159
+ ),
160
+ [closeOnBackdropPress]
161
+ );
162
+
163
+ // Handle component
164
+ const renderHandle = useCallback(() => {
165
+ if (!enableHandle) return null;
166
+
167
+ return (
168
+ <View className="items-center pt-2 pb-1">
169
+ <View
170
+ className={cn(
171
+ "w-10 h-1 rounded-full",
172
+ isDark ? "bg-gray-600" : "bg-gray-300"
173
+ )}
174
+ />
175
+ </View>
176
+ );
177
+ }, [enableHandle, isDark]);
178
+
179
+ const ContentWrapper = scrollable ? BottomSheetScrollView : BottomSheetView;
180
+
181
+ return (
182
+ <GorhomBottomSheet
183
+ ref={ref}
184
+ index={index}
185
+ snapPoints={snapPoints}
186
+ onChange={handleSheetChanges}
187
+ backdropComponent={enableBackdrop ? renderBackdrop : null}
188
+ handleComponent={renderHandle}
189
+ enablePanDownToClose
190
+ backgroundStyle={[
191
+ styles.background,
192
+ { backgroundColor: isDark ? "#1e293b" : "#ffffff" },
193
+ ]}
194
+ style={styles.sheet}
195
+ >
196
+ <View
197
+ className={cn(
198
+ "flex-1",
199
+ isDark ? "bg-surface-dark" : "bg-white",
200
+ containerClassName
201
+ )}
202
+ >
203
+ {/* Header */}
204
+ {(title || showCloseButton) && (
205
+ <View
206
+ className={cn(
207
+ "flex-row items-center justify-between px-4 py-3 border-b",
208
+ isDark ? "border-gray-700" : "border-gray-100"
209
+ )}
210
+ >
211
+ <Text
212
+ className={cn(
213
+ "text-lg font-semibold",
214
+ isDark ? "text-text-dark" : "text-text-light"
215
+ )}
216
+ >
217
+ {title || ""}
218
+ </Text>
219
+ {showCloseButton && (
220
+ <TouchableOpacity
221
+ onPress={handleClose}
222
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
223
+ className="p-1"
224
+ >
225
+ <Ionicons
226
+ name="close"
227
+ size={24}
228
+ color={isDark ? "#f8fafc" : "#0f172a"}
229
+ />
230
+ </TouchableOpacity>
231
+ )}
232
+ </View>
233
+ )}
234
+
235
+ {/* Content */}
236
+ <ContentWrapper
237
+ style={styles.contentContainer}
238
+ contentContainerStyle={[
239
+ styles.content,
240
+ scrollable && styles.scrollContent,
241
+ ]}
242
+ >
243
+ <View className={cn("flex-1", contentClassName)}>{children}</View>
244
+ </ContentWrapper>
245
+ </View>
246
+ </GorhomBottomSheet>
247
+ );
248
+ }
249
+ );
250
+
251
+ BottomSheet.displayName = "BottomSheet";
252
+
253
+ const styles = StyleSheet.create({
254
+ sheet: {
255
+ shadowColor: "#000",
256
+ shadowOffset: { width: 0, height: -4 },
257
+ shadowOpacity: 0.1,
258
+ shadowRadius: 8,
259
+ elevation: 5,
260
+ },
261
+ background: {
262
+ borderTopLeftRadius: 24,
263
+ borderTopRightRadius: 24,
264
+ },
265
+ contentContainer: {
266
+ flex: 1,
267
+ },
268
+ content: {
269
+ flex: 1,
270
+ },
271
+ scrollContent: {
272
+ paddingBottom: 40,
273
+ },
274
+ });
275
+
276
+ export function useBottomSheet() {
277
+ const ref = useRef<BottomSheetRef>(null);
278
+
279
+ const open = useCallback((snapIndex = 0) => {
280
+ ref.current?.snapToIndex(snapIndex);
281
+ }, []);
282
+
283
+ const close = useCallback(() => {
284
+ ref.current?.close();
285
+ }, []);
286
+
287
+ const snapTo = useCallback((index: number) => {
288
+ ref.current?.snapToIndex(index);
289
+ }, []);
290
+
291
+ const expand = useCallback(() => {
292
+ ref.current?.expand();
293
+ }, []);
294
+
295
+ const collapse = useCallback(() => {
296
+ ref.current?.collapse();
297
+ }, []);
298
+
299
+ return {
300
+ ref,
301
+ open,
302
+ close,
303
+ snapTo,
304
+ expand,
305
+ collapse,
306
+ };
307
+ }
@@ -22,8 +22,10 @@ interface ButtonProps extends PressableProps {
22
22
 
23
23
  const variantStyles: Record<ButtonVariant, string> = {
24
24
  primary: "bg-primary-600 active:bg-primary-700",
25
- secondary: "bg-gray-200 dark:bg-gray-700 active:bg-gray-300 dark:active:bg-gray-600",
26
- outline: "border-2 border-gray-300 dark:border-gray-600 bg-transparent active:bg-gray-100 dark:active:bg-gray-800",
25
+ secondary:
26
+ "bg-gray-200 dark:bg-gray-700 active:bg-gray-300 dark:active:bg-gray-600",
27
+ outline:
28
+ "border-2 border-gray-300 dark:border-gray-600 bg-transparent active:bg-gray-100 dark:active:bg-gray-800",
27
29
  ghost: "bg-transparent active:bg-gray-100 dark:active:bg-gray-800",
28
30
  danger: "bg-red-600 active:bg-red-700",
29
31
  };
@@ -68,6 +70,8 @@ export const Button = forwardRef<View, ButtonProps>(
68
70
  <Pressable
69
71
  ref={ref}
70
72
  disabled={isDisabled}
73
+ accessibilityRole="button"
74
+ accessibilityState={{ disabled: isDisabled }}
71
75
  className={cn(
72
76
  "flex-row items-center justify-center rounded-xl",
73
77
  variantStyles[variant],
@@ -79,7 +83,11 @@ export const Button = forwardRef<View, ButtonProps>(
79
83
  >
80
84
  {isLoading ? (
81
85
  <ActivityIndicator
82
- color={variant === "primary" || variant === "danger" ? "#ffffff" : "#3b82f6"}
86
+ color={
87
+ variant === "primary" || variant === "danger"
88
+ ? "#ffffff"
89
+ : "#3b82f6"
90
+ }
83
91
  size="small"
84
92
  />
85
93
  ) : typeof children === "string" ? (