@croacroa/react-native-template 1.0.0 → 2.0.1
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/.github/workflows/ci.yml +187 -184
- package/.github/workflows/eas-build.yml +55 -55
- package/.github/workflows/eas-update.yml +50 -50
- package/CHANGELOG.md +106 -106
- package/CONTRIBUTING.md +377 -377
- package/README.md +399 -399
- package/__tests__/components/snapshots.test.tsx +131 -0
- package/__tests__/integration/auth-api.test.tsx +227 -0
- package/__tests__/performance/VirtualizedList.perf.test.tsx +362 -0
- package/app/(public)/onboarding.tsx +5 -5
- package/app.config.ts +45 -2
- package/assets/images/.gitkeep +7 -7
- package/components/onboarding/OnboardingScreen.tsx +370 -370
- package/components/onboarding/index.ts +2 -2
- package/components/providers/SuspenseBoundary.tsx +357 -0
- package/components/providers/index.ts +21 -0
- package/components/ui/Avatar.tsx +316 -316
- package/components/ui/Badge.tsx +416 -416
- package/components/ui/BottomSheet.tsx +307 -307
- package/components/ui/Checkbox.tsx +261 -261
- package/components/ui/OptimizedImage.tsx +369 -369
- package/components/ui/Select.tsx +240 -240
- package/components/ui/VirtualizedList.tsx +285 -0
- package/components/ui/index.ts +23 -18
- package/constants/config.ts +97 -54
- 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/hooks/index.ts +27 -25
- package/hooks/useApi.ts +102 -5
- package/hooks/useAuth.tsx +82 -0
- package/hooks/useBiometrics.ts +295 -295
- package/hooks/useDeepLinking.ts +256 -256
- package/hooks/useMFA.ts +499 -0
- package/hooks/useNotifications.ts +39 -0
- package/hooks/useOffline.ts +60 -6
- package/hooks/usePerformance.ts +434 -434
- package/hooks/useTheme.tsx +76 -0
- package/hooks/useUpdates.ts +358 -358
- package/i18n/index.ts +194 -77
- package/i18n/locales/ar.json +101 -0
- package/i18n/locales/de.json +101 -0
- package/i18n/locales/en.json +101 -101
- package/i18n/locales/es.json +101 -0
- package/i18n/locales/fr.json +101 -101
- package/jest.config.js +4 -4
- 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 -0
- package/maestro/flows/mfa-setup.yaml +86 -0
- package/maestro/flows/navigation.yaml +68 -68
- package/maestro/flows/offline-conflict.yaml +101 -0
- package/maestro/flows/offline-sync.yaml +128 -0
- package/maestro/flows/offline.yaml +60 -60
- package/maestro/flows/register.yaml +94 -94
- package/package.json +175 -170
- package/services/analytics.ts +428 -428
- package/services/api.ts +340 -340
- package/services/authAdapter.ts +333 -333
- package/services/backgroundSync.ts +626 -0
- package/services/index.ts +54 -22
- package/services/security.ts +286 -0
- package/tailwind.config.js +47 -47
- package/utils/accessibility.ts +446 -446
- package/utils/index.ts +52 -43
- package/utils/validation.ts +2 -1
- package/utils/withAccessibility.tsx +272 -0
|
@@ -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
|
+
}
|