@umituz/react-native-design-system 2.3.8 → 2.3.10

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.
@@ -0,0 +1,122 @@
1
+ import React, { forwardRef, useCallback, useMemo, useImperativeHandle, useRef } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import GorhomBottomSheet, {
4
+ BottomSheetView,
5
+ BottomSheetBackdrop,
6
+ type BottomSheetBackdropProps,
7
+ } from '@gorhom/bottom-sheet';
8
+ import { useAppDesignTokens } from '../../../theme';
9
+ import type {
10
+ BottomSheetConfig,
11
+ BottomSheetRef,
12
+ BottomSheetProps,
13
+ } from '../types/BottomSheet';
14
+ import { BottomSheetUtils } from '../types/BottomSheet';
15
+
16
+ export const BottomSheet = forwardRef<BottomSheetRef, BottomSheetProps>((props, ref) => {
17
+ const {
18
+ children,
19
+ preset = 'medium',
20
+ snapPoints: customSnapPoints,
21
+ initialIndex,
22
+ enableBackdrop = true,
23
+ backdropAppearsOnIndex,
24
+ backdropDisappearsOnIndex,
25
+ keyboardBehavior = 'interactive',
26
+ enableHandleIndicator = true,
27
+ enablePanDownToClose = true,
28
+ enableDynamicSizing = false,
29
+ onChange,
30
+ onClose,
31
+ } = props;
32
+
33
+ const tokens = useAppDesignTokens();
34
+ const sheetRef = useRef<GorhomBottomSheet>(null);
35
+
36
+ const config: BottomSheetConfig = useMemo(() => {
37
+ if (customSnapPoints) {
38
+ return BottomSheetUtils.createConfig({
39
+ snapPoints: customSnapPoints,
40
+ initialIndex,
41
+ enableBackdrop,
42
+ backdropAppearsOnIndex,
43
+ backdropDisappearsOnIndex,
44
+ keyboardBehavior,
45
+ enableHandleIndicator,
46
+ enablePanDownToClose,
47
+ enableDynamicSizing,
48
+ });
49
+ }
50
+ return BottomSheetUtils.getPreset(preset);
51
+ }, [preset, customSnapPoints, initialIndex, enableBackdrop, backdropAppearsOnIndex, backdropDisappearsOnIndex, keyboardBehavior, enableHandleIndicator, enablePanDownToClose, enableDynamicSizing]);
52
+
53
+ const renderBackdrop = useCallback(
54
+ (backdropProps: BottomSheetBackdropProps) =>
55
+ enableBackdrop ? (
56
+ <BottomSheetBackdrop
57
+ {...backdropProps}
58
+ appearsOnIndex={config.backdropAppearsOnIndex ?? 0}
59
+ disappearsOnIndex={config.backdropDisappearsOnIndex ?? -1}
60
+ opacity={0.5}
61
+ pressBehavior="close"
62
+ />
63
+ ) : null,
64
+ [enableBackdrop, config.backdropAppearsOnIndex, config.backdropDisappearsOnIndex]
65
+ );
66
+
67
+ const handleSheetChange = useCallback(
68
+ (index: number) => {
69
+ onChange?.(index);
70
+ if (index === -1) onClose?.();
71
+ },
72
+ [onChange, onClose]
73
+ );
74
+
75
+ useImperativeHandle(ref, () => ({
76
+ snapToIndex: (index: number) => sheetRef.current?.snapToIndex(index),
77
+ snapToPosition: (pos: string | number) => sheetRef.current?.snapToPosition(pos),
78
+ expand: () => sheetRef.current?.expand(),
79
+ collapse: () => sheetRef.current?.collapse(),
80
+ close: () => sheetRef.current?.close(),
81
+ }));
82
+
83
+ if (!config.snapPoints || config.snapPoints.length === 0) return null;
84
+
85
+ return (
86
+ <GorhomBottomSheet
87
+ ref={sheetRef}
88
+ index={-1}
89
+ snapPoints={config.snapPoints}
90
+ enableDynamicSizing={config.enableDynamicSizing}
91
+ backdropComponent={renderBackdrop}
92
+ keyboardBehavior={config.keyboardBehavior}
93
+ enableHandlePanningGesture={config.enableHandleIndicator}
94
+ enablePanDownToClose={config.enablePanDownToClose}
95
+ onChange={handleSheetChange}
96
+ backgroundStyle={[styles.background, { backgroundColor: tokens.colors.surface }]}
97
+ handleIndicatorStyle={[styles.handleIndicator, { backgroundColor: tokens.colors.border }]}
98
+ >
99
+ <BottomSheetView style={styles.contentContainer}>
100
+ {children}
101
+ </BottomSheetView>
102
+ </GorhomBottomSheet>
103
+ );
104
+ });
105
+
106
+ BottomSheet.displayName = 'BottomSheet';
107
+
108
+ const styles = StyleSheet.create({
109
+ background: {
110
+ borderTopLeftRadius: 16,
111
+ borderTopRightRadius: 16,
112
+ },
113
+ handleIndicator: {
114
+ width: 40,
115
+ height: 4,
116
+ borderRadius: 2,
117
+ },
118
+ contentContainer: {
119
+ flex: 1,
120
+ },
121
+ });
122
+
@@ -0,0 +1,124 @@
1
+ import React, { forwardRef, useCallback, useMemo, useImperativeHandle, useRef } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import {
4
+ BottomSheetModal as GorhomBottomSheetModal,
5
+ BottomSheetView,
6
+ BottomSheetBackdrop,
7
+ type BottomSheetBackdropProps,
8
+ } from '@gorhom/bottom-sheet';
9
+ import { useAppDesignTokens } from '../../../theme';
10
+ import type {
11
+ BottomSheetConfig,
12
+ BottomSheetModalRef,
13
+ BottomSheetModalProps,
14
+ } from '../types/BottomSheet';
15
+ import { BottomSheetUtils } from '../types/BottomSheet';
16
+
17
+ export const BottomSheetModal = forwardRef<BottomSheetModalRef, BottomSheetModalProps>((props, ref) => {
18
+ const {
19
+ children,
20
+ preset = 'medium',
21
+ snapPoints: customSnapPoints,
22
+ initialIndex,
23
+ enableBackdrop = true,
24
+ backdropAppearsOnIndex,
25
+ backdropDisappearsOnIndex,
26
+ keyboardBehavior = 'interactive',
27
+ enableHandleIndicator = true,
28
+ enablePanDownToClose = true,
29
+ enableDynamicSizing = false,
30
+ onChange,
31
+ onDismiss,
32
+ backgroundColor,
33
+ } = props;
34
+
35
+ const tokens = useAppDesignTokens();
36
+ const modalRef = useRef<GorhomBottomSheetModal>(null);
37
+
38
+ const config: BottomSheetConfig = useMemo(() => {
39
+ if (customSnapPoints) {
40
+ return BottomSheetUtils.createConfig({
41
+ snapPoints: customSnapPoints,
42
+ initialIndex,
43
+ enableBackdrop,
44
+ backdropAppearsOnIndex,
45
+ backdropDisappearsOnIndex,
46
+ keyboardBehavior,
47
+ enableHandleIndicator,
48
+ enablePanDownToClose,
49
+ enableDynamicSizing,
50
+ });
51
+ }
52
+ return BottomSheetUtils.getPreset(preset);
53
+ }, [preset, customSnapPoints, initialIndex, enableBackdrop, backdropAppearsOnIndex, backdropDisappearsOnIndex, keyboardBehavior, enableHandleIndicator, enablePanDownToClose, enableDynamicSizing]);
54
+
55
+ const renderBackdrop = useCallback(
56
+ (backdropProps: BottomSheetBackdropProps) =>
57
+ enableBackdrop ? (
58
+ <BottomSheetBackdrop
59
+ {...backdropProps}
60
+ appearsOnIndex={config.backdropAppearsOnIndex ?? 0}
61
+ disappearsOnIndex={config.backdropDisappearsOnIndex ?? -1}
62
+ opacity={0.5}
63
+ pressBehavior="close"
64
+ />
65
+ ) : null,
66
+ [enableBackdrop, config.backdropAppearsOnIndex, config.backdropDisappearsOnIndex]
67
+ );
68
+
69
+ const handleSheetChange = useCallback(
70
+ (index: number) => {
71
+ onChange?.(index);
72
+ if (index === -1) onDismiss?.();
73
+ },
74
+ [onChange, onDismiss]
75
+ );
76
+
77
+ useImperativeHandle(ref, () => ({
78
+ present: () => modalRef.current?.present(),
79
+ dismiss: () => modalRef.current?.dismiss(),
80
+ snapToIndex: (index: number) => modalRef.current?.snapToIndex(index),
81
+ snapToPosition: (pos: string | number) => modalRef.current?.snapToPosition(pos),
82
+ expand: () => modalRef.current?.expand(),
83
+ collapse: () => modalRef.current?.collapse(),
84
+ }));
85
+
86
+ return (
87
+ <GorhomBottomSheetModal
88
+ ref={modalRef}
89
+ index={-1}
90
+ snapPoints={config.snapPoints}
91
+ enableDynamicSizing={config.enableDynamicSizing}
92
+ backdropComponent={renderBackdrop}
93
+ keyboardBehavior={config.keyboardBehavior}
94
+ enableHandlePanningGesture={config.enableHandleIndicator}
95
+ enablePanDownToClose={config.enablePanDownToClose}
96
+ onChange={handleSheetChange}
97
+ onDismiss={onDismiss}
98
+ backgroundStyle={[styles.background, { backgroundColor: backgroundColor || tokens.colors.surface }]}
99
+ handleIndicatorStyle={[styles.handleIndicator, { backgroundColor: tokens.colors.border }]}
100
+ >
101
+ <BottomSheetView style={styles.contentContainer}>
102
+ {children}
103
+ </BottomSheetView>
104
+ </GorhomBottomSheetModal>
105
+ );
106
+ });
107
+
108
+ BottomSheetModal.displayName = 'BottomSheetModal';
109
+
110
+ const styles = StyleSheet.create({
111
+ background: {
112
+ borderTopLeftRadius: 16,
113
+ borderTopRightRadius: 16,
114
+ },
115
+ handleIndicator: {
116
+ width: 40,
117
+ height: 4,
118
+ borderRadius: 2,
119
+ },
120
+ contentContainer: {
121
+ flex: 1,
122
+ },
123
+ });
124
+
@@ -0,0 +1,11 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
3
+
4
+ interface SafeBottomSheetModalProviderProps {
5
+ children: ReactNode;
6
+ }
7
+
8
+ export const SafeBottomSheetModalProvider = ({ children }: SafeBottomSheetModalProviderProps) => (
9
+ <BottomSheetModalProvider>{children}</BottomSheetModalProvider>
10
+ );
11
+
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Presentation - Filter Bottom Sheet component
3
+ *
4
+ * Advanced filtering UI using @umituz/react-native-bottom-sheet
5
+ */
6
+
7
+ import React, { forwardRef, useCallback } from 'react';
8
+ import { View, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
9
+ import { BottomSheetModal } from '../BottomSheetModal';
10
+ import type { BottomSheetModalRef } from '../../types/BottomSheet';
11
+ import { AtomicText, AtomicIcon, AtomicButton } from '../../../atoms';
12
+ import { useAppDesignTokens } from '../../../../theme';
13
+ import type { FilterOption, FilterCategory } from '../../types/Filter';
14
+ import { FilterUtils } from '../../types/Filter';
15
+
16
+ export interface FilterBottomSheetProps {
17
+ readonly categories: FilterCategory[];
18
+ readonly selectedIds: string[];
19
+ readonly onFilterPress: (id: string, categoryId: string) => void;
20
+ readonly onClearFilters: () => void;
21
+ readonly onDismiss?: () => void;
22
+ readonly title?: string;
23
+ readonly clearLabel?: string;
24
+ readonly applyLabel?: string;
25
+ readonly defaultId?: string;
26
+ }
27
+
28
+ export const FilterBottomSheet = forwardRef<BottomSheetModalRef, FilterBottomSheetProps>(({
29
+ categories,
30
+ selectedIds,
31
+ onFilterPress,
32
+ onClearFilters,
33
+ onDismiss,
34
+ title,
35
+ clearLabel = 'Clear',
36
+ applyLabel = 'Apply',
37
+ defaultId = 'all'
38
+ }, ref) => {
39
+ const tokens = useAppDesignTokens();
40
+
41
+ const styles = React.useMemo(() => StyleSheet.create({
42
+ container: {
43
+ flex: 1,
44
+ padding: 16,
45
+ },
46
+ header: {
47
+ flexDirection: 'row',
48
+ justifyContent: 'space-between',
49
+ alignItems: 'center',
50
+ marginBottom: 20,
51
+ },
52
+ category: {
53
+ marginBottom: 24,
54
+ },
55
+ categoryTitle: {
56
+ marginBottom: 12,
57
+ opacity: 0.7,
58
+ },
59
+ optionsGrid: {
60
+ flexDirection: 'row',
61
+ flexWrap: 'wrap',
62
+ gap: 8,
63
+ },
64
+ option: {
65
+ flexDirection: 'row',
66
+ alignItems: 'center',
67
+ paddingHorizontal: 12,
68
+ paddingVertical: 8,
69
+ borderRadius: 20,
70
+ backgroundColor: tokens.colors.surfaceVariant,
71
+ gap: 6,
72
+ borderWidth: 1,
73
+ borderColor: 'transparent',
74
+ },
75
+ optionLeft: {
76
+ flexDirection: 'row',
77
+ alignItems: 'center',
78
+ gap: 6,
79
+ },
80
+ footer: {
81
+ marginTop: 16,
82
+ paddingBottom: 8,
83
+ }
84
+ }), [tokens]);
85
+
86
+ const renderOption = useCallback((option: FilterOption, categoryId: string) => {
87
+ const isSelected = selectedIds.includes(option.id);
88
+
89
+ return (
90
+ <TouchableOpacity
91
+ key={option.id}
92
+ style={[
93
+ styles.option,
94
+ isSelected && { backgroundColor: tokens.colors.primary + '15' }
95
+ ]}
96
+ onPress={() => onFilterPress(option.id, categoryId)}
97
+ >
98
+ <View style={styles.optionLeft}>
99
+ {option.icon && (
100
+ <AtomicIcon
101
+ name={option.icon as any}
102
+ size="sm"
103
+ color={isSelected ? 'primary' : 'secondary'}
104
+ />
105
+ )}
106
+ <AtomicText
107
+ type="bodyMedium"
108
+ style={[isSelected && { color: tokens.colors.primary, fontWeight: '600' }]}
109
+ >
110
+ {option.label}
111
+ </AtomicText>
112
+ </View>
113
+ {isSelected && (
114
+ <AtomicIcon name="checkmark" size="sm" color="primary" />
115
+ )}
116
+ </TouchableOpacity>
117
+ );
118
+ }, [selectedIds, tokens, onFilterPress]);
119
+
120
+ const renderCategory = useCallback((category: FilterCategory) => (
121
+ <View key={category.id} style={styles.category}>
122
+ <AtomicText type="labelLarge" style={styles.categoryTitle}>
123
+ {category.title}
124
+ </AtomicText>
125
+ <View style={styles.optionsGrid}>
126
+ {category.options.map(option => renderOption(option, category.id))}
127
+ </View>
128
+ </View>
129
+ ), [renderOption, styles]);
130
+
131
+
132
+ const hasActiveFilters = FilterUtils.hasActiveFilter(selectedIds, defaultId);
133
+
134
+ return (
135
+ <BottomSheetModal
136
+ ref={ref}
137
+ preset="medium"
138
+ onDismiss={onDismiss}
139
+ backgroundColor={tokens.colors.surface}
140
+ >
141
+ <View style={styles.container}>
142
+ <View style={styles.header}>
143
+ <AtomicText type="headlineSmall">{title || 'Filter'}</AtomicText>
144
+ {hasActiveFilters && (
145
+ <TouchableOpacity onPress={onClearFilters}>
146
+ <AtomicText type="labelLarge" color="error">{clearLabel}</AtomicText>
147
+ </TouchableOpacity>
148
+ )}
149
+ </View>
150
+
151
+ <ScrollView showsVerticalScrollIndicator={false}>
152
+ {categories.map(renderCategory)}
153
+ </ScrollView>
154
+
155
+ <View style={styles.footer}>
156
+ <AtomicButton
157
+ onPress={() => (ref as any).current?.dismiss()}
158
+ fullWidth
159
+ >
160
+ {applyLabel}
161
+ </AtomicButton>
162
+ </View>
163
+ </View>
164
+ </BottomSheetModal>
165
+ );
166
+ });
167
+
168
+ FilterBottomSheet.displayName = 'FilterBottomSheet';
@@ -0,0 +1,116 @@
1
+ import React, { useCallback } from "react";
2
+ import { View, StyleSheet, ScrollView, Modal, Pressable } from "react-native";
3
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
+ import { AtomicButton } from "../../../atoms";
5
+ import { useAppDesignTokens } from "../../../../theme";
6
+ import type { FilterOption } from "../../types/Filter";
7
+ import { FilterUtils } from "../../types/Filter";
8
+ import { FilterSheetHeader } from "./FilterSheetComponents/FilterSheetHeader";
9
+ import { FilterSheetOption } from "./FilterSheetComponents/FilterSheetOption";
10
+
11
+ export interface FilterSheetProps {
12
+ visible: boolean;
13
+ options: FilterOption[];
14
+ selectedIds: string[];
15
+ onFilterPress: (filterId: string) => void;
16
+ onClearFilters: () => void;
17
+ onClose?: () => void;
18
+ defaultFilterId?: string;
19
+ title?: string;
20
+ clearLabel?: string;
21
+ }
22
+
23
+ export const FilterSheet: React.FC<FilterSheetProps> = ({
24
+ visible,
25
+ options,
26
+ selectedIds,
27
+ onFilterPress,
28
+ onClearFilters,
29
+ onClose,
30
+ defaultFilterId = "all",
31
+ title,
32
+ clearLabel = "Clear"
33
+ }) => {
34
+ const tokens = useAppDesignTokens();
35
+ const insets = useSafeAreaInsets();
36
+
37
+ const hasActiveFilter = FilterUtils.hasActiveFilter(selectedIds, defaultFilterId);
38
+
39
+ const handleFilterPressWithClose = useCallback((id: string) => {
40
+ onFilterPress(id);
41
+ onClose?.();
42
+ }, [onFilterPress, onClose]);
43
+
44
+ return (
45
+ <Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
46
+ <Pressable style={styles.backdrop} onPress={onClose}>
47
+ <Pressable
48
+ style={[styles.sheet, { backgroundColor: tokens.colors.surface, paddingBottom: insets.bottom }]}
49
+ onPress={(e) => e.stopPropagation()}
50
+ >
51
+ <View style={[styles.handle, { backgroundColor: tokens.colors.border }]} />
52
+
53
+ <FilterSheetHeader
54
+ title={title || "Filter"}
55
+ onClose={() => onClose?.()}
56
+ tokens={tokens}
57
+ />
58
+
59
+ <ScrollView style={styles.optionsList} showsVerticalScrollIndicator={false}>
60
+ {options.map((option) => (
61
+ <FilterSheetOption
62
+ key={option.id}
63
+ option={option}
64
+ isSelected={selectedIds.includes(option.id)}
65
+ onPress={handleFilterPressWithClose}
66
+ tokens={tokens}
67
+ />
68
+ ))}
69
+ </ScrollView>
70
+
71
+ {hasActiveFilter && (
72
+ <View style={[styles.footer, { borderTopColor: tokens.colors.border, borderTopWidth: tokens.borders.width.thin }]}>
73
+ <AtomicButton variant="outline" onPress={onClearFilters} fullWidth>
74
+ {clearLabel}
75
+ </AtomicButton>
76
+ </View>
77
+ )}
78
+ </Pressable>
79
+ </Pressable>
80
+ </Modal>
81
+ );
82
+ };
83
+
84
+ FilterSheet.displayName = "FilterSheet";
85
+
86
+ const styles = StyleSheet.create({
87
+ backdrop: {
88
+ flex: 1,
89
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
90
+ justifyContent: "flex-end",
91
+ },
92
+ sheet: {
93
+ borderTopLeftRadius: 16,
94
+ borderTopRightRadius: 16,
95
+ maxHeight: "80%",
96
+ },
97
+ handle: {
98
+ width: 40,
99
+ height: 4,
100
+ borderRadius: 2,
101
+ alignSelf: "center",
102
+ marginTop: 8,
103
+ marginBottom: 8,
104
+ },
105
+ optionsList: {
106
+ maxHeight: 400,
107
+ paddingVertical: 8,
108
+ },
109
+ footer: {
110
+ paddingHorizontal: 20,
111
+ paddingTop: 16,
112
+ paddingBottom: 8,
113
+ },
114
+ });
115
+
116
+
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, TouchableOpacity } from 'react-native';
3
+ import { AtomicText, AtomicIcon } from '../../../../../atoms';
4
+ import type { useAppDesignTokens } from '../../../../../theme';
5
+
6
+ interface FilterSheetHeaderProps {
7
+ title: string;
8
+ onClose: () => void;
9
+ tokens: ReturnType<typeof useAppDesignTokens>;
10
+ }
11
+
12
+ export const FilterSheetHeader = ({ title, onClose, tokens }: FilterSheetHeaderProps) => (
13
+ <View style={[styles.header, { borderBottomColor: tokens.colors.border, borderBottomWidth: tokens.borders.width.thin }]}>
14
+ <AtomicText type="headlineMedium" style={styles.title}>{title}</AtomicText>
15
+ <TouchableOpacity onPress={onClose} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
16
+ <AtomicIcon name="close" size="md" color="primary" />
17
+ </TouchableOpacity>
18
+ </View>
19
+ );
20
+
21
+ const styles = StyleSheet.create({
22
+ header: {
23
+ flexDirection: 'row',
24
+ alignItems: 'center',
25
+ justifyContent: 'space-between',
26
+ paddingHorizontal: 20,
27
+ paddingBottom: 16,
28
+ paddingTop: 8,
29
+ },
30
+ title: {
31
+ fontWeight: '600',
32
+ },
33
+ });
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, TouchableOpacity } from 'react-native';
3
+ import { AtomicText, AtomicIcon } from '../../../../../atoms';
4
+ import type { useAppDesignTokens } from '../../../../../theme';
5
+ import type { FilterOption } from '../../../types/Filter';
6
+
7
+ interface FilterSheetOptionProps {
8
+ option: FilterOption;
9
+ isSelected: boolean;
10
+ onPress: (id: string) => void;
11
+ tokens: ReturnType<typeof useAppDesignTokens>;
12
+ }
13
+
14
+ export const FilterSheetOption = ({ option, isSelected, onPress, tokens }: FilterSheetOptionProps) => (
15
+ <TouchableOpacity
16
+ onPress={() => onPress(option.id)}
17
+ style={[
18
+ styles.option,
19
+ { borderBottomColor: tokens.colors.borderLight, borderBottomWidth: tokens.borders.width.thin },
20
+ isSelected && { backgroundColor: tokens.colors.primary + '15' }
21
+ ]}
22
+ >
23
+ <View style={styles.optionContent}>
24
+ {option.icon && (
25
+ <AtomicIcon
26
+ name={option.icon as any}
27
+ size="md"
28
+ color={isSelected ? 'primary' : 'secondary'}
29
+ />
30
+ )}
31
+ <AtomicText
32
+ type="bodyLarge"
33
+ style={[styles.optionLabel, isSelected && { color: tokens.colors.primary, fontWeight: '600' }]}
34
+ >
35
+ {option.label}
36
+ </AtomicText>
37
+ </View>
38
+ {isSelected && <AtomicIcon name="checkmark-circle" size="md" color="primary" />}
39
+ </TouchableOpacity>
40
+ );
41
+
42
+ const styles = StyleSheet.create({
43
+ option: {
44
+ flexDirection: 'row',
45
+ alignItems: 'center',
46
+ justifyContent: 'space-between',
47
+ paddingHorizontal: 20,
48
+ paddingVertical: 16,
49
+ },
50
+ optionContent: {
51
+ flexDirection: 'row',
52
+ alignItems: 'center',
53
+ gap: 12,
54
+ flex: 1,
55
+ },
56
+ optionLabel: {
57
+ flex: 1,
58
+ },
59
+ });
@@ -0,0 +1,16 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import type { BottomSheetRef, UseBottomSheetReturn } from '../types/BottomSheet';
3
+
4
+ export const useBottomSheet = (): UseBottomSheetReturn => {
5
+ const sheetRef = useRef<BottomSheetRef>(null);
6
+
7
+ const open = useCallback(() => sheetRef.current?.snapToIndex(0), []);
8
+ const close = useCallback(() => sheetRef.current?.close(), []);
9
+ const expand = useCallback(() => sheetRef.current?.expand(), []);
10
+ const collapse = useCallback(() => sheetRef.current?.collapse(), []);
11
+ const snapToIndex = useCallback((index: number) => sheetRef.current?.snapToIndex(index), []);
12
+ const snapToPosition = useCallback((pos: string | number) => sheetRef.current?.snapToPosition(pos), []);
13
+
14
+ return { sheetRef, open, close, expand, collapse, snapToIndex, snapToPosition };
15
+ };
16
+
@@ -0,0 +1,17 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import type { BottomSheetModalRef, UseBottomSheetModalReturn } from '../types/BottomSheet';
3
+
4
+ export const useBottomSheetModal = (): UseBottomSheetModalReturn => {
5
+ const modalRef = useRef<BottomSheetModalRef>(null);
6
+
7
+ const present = useCallback(() => modalRef.current?.present(), []);
8
+ const dismiss = useCallback(() => modalRef.current?.dismiss(), []);
9
+ const expand = useCallback(() => modalRef.current?.expand(), []);
10
+ const collapse = useCallback(() => modalRef.current?.collapse(), []);
11
+ const snapToIndex = useCallback((index: number) => modalRef.current?.snapToIndex(index), []);
12
+ const snapToPosition = useCallback((pos: string | number) => modalRef.current?.snapToPosition(pos), []);
13
+
14
+ return { modalRef, present, dismiss, expand, collapse, snapToIndex, snapToPosition };
15
+ };
16
+
17
+