ota-components-module 1.3.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 (36) hide show
  1. package/README.md +179 -0
  2. package/assets/images/ic_camera.svg +3 -0
  3. package/assets/images/ic_close.svg +8 -0
  4. package/assets/images/ic_folder.svg +3 -0
  5. package/assets/images/placeholder.png +0 -0
  6. package/expo-env.d.ts +7 -0
  7. package/mri-manifest.json +10 -0
  8. package/package.json +28 -0
  9. package/src/button/ThemedButton.tsx +120 -0
  10. package/src/feedback/ActivityLoader.tsx +84 -0
  11. package/src/feedback/CustomAlert.tsx +143 -0
  12. package/src/feedback/DeleteImageConfirmationDialog.tsx +58 -0
  13. package/src/feedback/ProgressBar.tsx +58 -0
  14. package/src/image/ImagePickerBottomSheet.tsx +61 -0
  15. package/src/image/ImagePickerView.tsx +103 -0
  16. package/src/image/MultipleImagePreview.tsx +424 -0
  17. package/src/image/StackedImage.tsx +155 -0
  18. package/src/index.ts +68 -0
  19. package/src/input/CustomDropdown.tsx +142 -0
  20. package/src/input/CustomInput.tsx +101 -0
  21. package/src/input/FormField.tsx +358 -0
  22. package/src/input/KeyboardScrollView.tsx +131 -0
  23. package/src/input/SearchViewInput.tsx +183 -0
  24. package/src/layout/BottomSheetDialog.tsx +208 -0
  25. package/src/layout/BottomTwoButtonLayoutComponent.tsx +153 -0
  26. package/src/layout/CardView.tsx +101 -0
  27. package/src/layout/PropertyHeaderComponent.tsx +110 -0
  28. package/src/list/SearchableList.tsx +273 -0
  29. package/src/models/PropertyImage.ts +20 -0
  30. package/src/typography/Label.tsx +225 -0
  31. package/src/utils/BaseStyle.ts +46 -0
  32. package/src/utils/Strings.ts +1 -0
  33. package/src/utils/TextConstants.ts +24 -0
  34. package/src/utils/Utils.ts +11 -0
  35. package/src/webbaseview/WebBaseView.tsx +26 -0
  36. package/tsconfig.json +9 -0
@@ -0,0 +1,208 @@
1
+ import React, {
2
+ useEffect,
3
+ useImperativeHandle,
4
+ useRef,
5
+ useState,
6
+ } from 'react';
7
+ import {
8
+ View,
9
+ StyleSheet,
10
+ Modal,
11
+ TouchableWithoutFeedback,
12
+ Animated,
13
+ Dimensions,
14
+ PanResponder,
15
+ ViewStyle,
16
+ BackHandler,
17
+ } from 'react-native';
18
+
19
+ const screenHeight = Dimensions.get('window').height;
20
+
21
+ type BottomSheetDialogProps = {
22
+ visible: boolean;
23
+ onClose: () => void;
24
+ children: React.ReactNode;
25
+ height?: number | string;
26
+ containerStyle?: ViewStyle;
27
+ contentStyle?: ViewStyle;
28
+ closeOnBackdropPress?: boolean;
29
+ isTranslucent?: boolean;
30
+ closeOnBackButtonPress?: boolean;
31
+ };
32
+
33
+ export type BottomSheetDialogRef = {
34
+ closeBottomSheet: () => void;
35
+ };
36
+
37
+ const BottomSheetDialog = React.forwardRef<BottomSheetDialogRef, BottomSheetDialogProps>(
38
+ (
39
+ {
40
+ visible,
41
+ onClose,
42
+ children,
43
+ height = screenHeight * 0.6,
44
+ containerStyle,
45
+ contentStyle,
46
+ closeOnBackdropPress = true,
47
+ isTranslucent = true,
48
+ closeOnBackButtonPress = true,
49
+ },
50
+ ref
51
+ ) => {
52
+ const translateY = useRef(new Animated.Value(screenHeight)).current;
53
+ const opacity = useRef(new Animated.Value(0)).current;
54
+
55
+ const isAnimating = useRef(false);
56
+ const isVisibleRef = useRef(false);
57
+
58
+ useImperativeHandle(ref, () => ({
59
+ closeBottomSheet,
60
+ }));
61
+
62
+ useEffect(() => {
63
+ if (visible) {
64
+ openBottomSheet();
65
+ } else {
66
+ closeBottomSheet(); // Animate close
67
+ }
68
+ }, [visible]);
69
+
70
+ const openBottomSheet = () => {
71
+ if (isAnimating.current || isVisibleRef.current) return;
72
+ isAnimating.current = true;
73
+
74
+ Animated.parallel([
75
+ Animated.timing(translateY, {
76
+ toValue: 0,
77
+ duration: 300,
78
+ useNativeDriver: true,
79
+ }),
80
+ Animated.timing(opacity, {
81
+ toValue: 1,
82
+ duration: 300,
83
+ useNativeDriver: true,
84
+ }),
85
+ ]).start(() => {
86
+ isAnimating.current = false;
87
+ isVisibleRef.current = true;
88
+ });
89
+ };
90
+
91
+ const closeBottomSheet = () => {
92
+ if (isAnimating.current || !isVisibleRef.current) return;
93
+ isAnimating.current = true;
94
+
95
+ Animated.parallel([
96
+ Animated.timing(translateY, {
97
+ toValue: screenHeight,
98
+ duration: 300,
99
+ useNativeDriver: true,
100
+ }),
101
+ Animated.timing(opacity, {
102
+ toValue: 0,
103
+ duration: 300,
104
+ useNativeDriver: true,
105
+ }),
106
+ ]).start(() => {
107
+ isAnimating.current = false;
108
+ isVisibleRef.current = false;
109
+ onClose?.();
110
+ });
111
+ };
112
+
113
+ const handleBackdropPress = () => {
114
+ if (closeOnBackdropPress) {
115
+ closeBottomSheet();
116
+ }
117
+ };
118
+
119
+ const panResponder = useRef(
120
+ PanResponder.create({
121
+ onStartShouldSetPanResponder: () => true,
122
+ onMoveShouldSetPanResponder: (_, gestureState) => gestureState.dy > 0,
123
+ onPanResponderMove: (_, gestureState) => {
124
+ if (gestureState.dy > 0) {
125
+ translateY.setValue(gestureState.dy);
126
+ }
127
+ },
128
+ onPanResponderRelease: (_, gestureState) => {
129
+ if (gestureState.dy > 100) {
130
+ closeBottomSheet();
131
+ } else {
132
+ Animated.spring(translateY, {
133
+ toValue: 0,
134
+ useNativeDriver: true,
135
+ }).start();
136
+ }
137
+ },
138
+ })
139
+ ).current;
140
+
141
+ return (
142
+ <Modal transparent visible={visible} animationType="none" statusBarTranslucent={isTranslucent}
143
+ onRequestClose={() => {
144
+ onClose?.();
145
+ }} >
146
+ <View style={styles.modalContainer}>
147
+ <TouchableWithoutFeedback onPress={handleBackdropPress}>
148
+ <Animated.View style={[styles.backdrop, { opacity }]} />
149
+ </TouchableWithoutFeedback>
150
+
151
+ <Animated.View
152
+ style={[
153
+ styles.bottomSheetContainer,
154
+ containerStyle,
155
+ {
156
+ maxHeight:"100%",
157
+ height,
158
+ transform: [{ translateY }],
159
+ },
160
+ ]}
161
+ >
162
+ <View style={styles.dragHandleContainer} {...panResponder.panHandlers}>
163
+ <View style={styles.dragHandle} />
164
+ </View>
165
+ <View style={[styles.contentContainer, contentStyle]}>
166
+ {children}
167
+ </View>
168
+ </Animated.View>
169
+ </View>
170
+ </Modal>
171
+ );
172
+ }
173
+ );
174
+
175
+ const styles = StyleSheet.create({
176
+ modalContainer: {
177
+ flex: 1,
178
+ justifyContent: 'flex-end',
179
+ },
180
+ backdrop: {
181
+ ...StyleSheet.absoluteFillObject,
182
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
183
+ },
184
+ bottomSheetContainer: {
185
+ backgroundColor: 'white',
186
+ borderTopLeftRadius: 20,
187
+ borderTopRightRadius: 20,
188
+ overflow: 'hidden',
189
+ },
190
+ dragHandleContainer: {
191
+ width: '100%',
192
+ height: 24,
193
+ alignItems: 'center',
194
+ justifyContent: 'center',
195
+ },
196
+ dragHandle: {
197
+ width: 40,
198
+ height: 4,
199
+ borderRadius: 2,
200
+ backgroundColor: '#ccc',
201
+ },
202
+ contentContainer: {
203
+ flex: 1,
204
+ padding: 16,
205
+ },
206
+ });
207
+
208
+ export default BottomSheetDialog;
@@ -0,0 +1,153 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, ViewStyle, Platform } from 'react-native';
3
+ import ThemedButton from '../button/ThemedButton';
4
+ import ProgressBar from '../feedback/ProgressBar';
5
+ import { Colors } from '../utils/BaseStyle';
6
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
7
+ import { isAndroid15AndAbove, isIOS } from '../utils/Utils';
8
+
9
+ interface BottomTwoButtonLayoutProps {
10
+ primaryButtonTestID?: string;
11
+ secondaryButtonTestID?: string;
12
+ primaryButtonText: string;
13
+ secondaryButtonText: string;
14
+ onPrimaryButtonPress: () => void;
15
+ onSecondaryButtonPress: () => void;
16
+ isPrimaryButtonDisabled?: boolean;
17
+ isSecondaryButtonDisabled?: boolean;
18
+ containerStyle?: ViewStyle;
19
+ // Progress bar props
20
+ showProgressBar?: boolean;
21
+ showButtons?: boolean;
22
+ progress?: number;
23
+ maxValue?: number;
24
+ minValue?: number;
25
+ primaryColor?: string;
26
+ backgroundColor?: string;
27
+ disabledButtonColor?: string;
28
+ }
29
+
30
+ /**
31
+ * A layout component with two buttons at the bottom and optional progress bar
32
+ */
33
+ const BottomTwoButtonLayoutComponent: React.FC<BottomTwoButtonLayoutProps> = ({
34
+ primaryButtonTestID,
35
+ secondaryButtonTestID,
36
+ primaryButtonText,
37
+ secondaryButtonText,
38
+ onPrimaryButtonPress,
39
+ onSecondaryButtonPress,
40
+ isPrimaryButtonDisabled = false,
41
+ isSecondaryButtonDisabled = false,
42
+ containerStyle,
43
+ showButtons = true,
44
+ // Progress bar props
45
+ showProgressBar = false,
46
+ progress = 0,
47
+ maxValue = 1,
48
+ minValue = 0,
49
+ primaryColor = Colors.lightThemePrimaryColor,
50
+ backgroundColor = Colors.whiteColor,
51
+ disabledButtonColor = '#B8D8E8'
52
+ }) => {
53
+
54
+ const insets = useSafeAreaInsets();
55
+ // const bottomPadding = ((isIOS || isAndroid15AndAbove) ? Math.max(insets.bottom, 20) : 0) + 20;
56
+ const styles = layoutStyles(showButtons, (isIOS) ? insets.bottom : insets.bottom + 20, backgroundColor);
57
+
58
+ return (
59
+ <View style={[
60
+ styles.buttonContainer,
61
+ containerStyle
62
+ ]}>
63
+ {/* Progress Bar */}
64
+ {showProgressBar && (
65
+ <View style={styles.progressBarContainer}>
66
+ <ProgressBar
67
+ progress={progress}
68
+ maxValue={maxValue}
69
+ minValue={minValue}
70
+ height={6}
71
+ backgroundColor={Colors.lightGrayColor || '#E5E5E5'}
72
+ progressColor={primaryColor}
73
+ borderRadius={3}
74
+ />
75
+ </View>
76
+ )}
77
+
78
+ {/* Buttons */}
79
+ {showButtons && (
80
+ <View style={styles.buttonsRow}>
81
+ <ThemedButton
82
+ testID={secondaryButtonTestID}
83
+ style={styles.secondaryButton}
84
+ title={secondaryButtonText}
85
+ onPress={onSecondaryButtonPress}
86
+ disabled={isSecondaryButtonDisabled}
87
+ type="secondary"
88
+ primaryColor={primaryColor}
89
+ backgroundColor={backgroundColor}
90
+ disabledColor={disabledButtonColor}
91
+ />
92
+ <ThemedButton
93
+ testID={primaryButtonTestID}
94
+ style={styles.primaryButton}
95
+ title={primaryButtonText}
96
+ onPress={onPrimaryButtonPress}
97
+ disabled={isPrimaryButtonDisabled}
98
+ type="primary"
99
+ primaryColor={primaryColor}
100
+ backgroundColor={backgroundColor}
101
+ disabledColor={disabledButtonColor}
102
+ />
103
+ </View>
104
+ )}
105
+ </View>
106
+ );
107
+ };
108
+
109
+ const layoutStyles = (showButtons: boolean, paddingBottom: number, backgroundColor: string) => StyleSheet.create({
110
+ buttonContainer: {
111
+ flexDirection: 'column',
112
+ justifyContent: showButtons ? 'space-between' : 'center',
113
+ paddingHorizontal: 30,
114
+ paddingTop: 20,
115
+ paddingBottom: paddingBottom,
116
+ backgroundColor: backgroundColor,
117
+ position: 'absolute',
118
+ bottom: 0,
119
+ left: 0,
120
+ right: 0,
121
+ borderTopWidth: 1,
122
+ borderTopColor: '#eee',
123
+ elevation: 5,
124
+ shadowColor: Colors.shadowColor,
125
+ shadowOffset: { width: 0, height: -3 },
126
+ shadowOpacity: 0.07,
127
+ shadowRadius: 5,
128
+ },
129
+ progressBarContainer: {
130
+ width: '100%',
131
+ marginBottom: 20,
132
+ justifyContent: 'center',
133
+ alignItems: 'center',
134
+ alignContent: 'center',
135
+ },
136
+ buttonsRow: {
137
+ flexDirection: 'row',
138
+ justifyContent: 'space-between',
139
+ width: '100%',
140
+ },
141
+ primaryButton: {
142
+ flex: 1,
143
+ marginLeft: 8,
144
+ height: 60
145
+ },
146
+ secondaryButton: {
147
+ flex: 1,
148
+ marginRight: 8,
149
+ height: 60
150
+ },
151
+ });
152
+
153
+ export default BottomTwoButtonLayoutComponent;
@@ -0,0 +1,101 @@
1
+ import React, { ReactNode } from 'react';
2
+ import { View, StyleSheet, ViewStyle, Platform } from 'react-native';
3
+ import { Colors } from '../utils/BaseStyle';
4
+
5
+ interface CardViewProps {
6
+ children: ReactNode;
7
+ style?: ViewStyle;
8
+ cardPadding?: number;
9
+ borderRadius?: number;
10
+ backgroundColor?: string;
11
+ elevation?: number;
12
+ shadowOpacity?: number;
13
+ }
14
+
15
+ /**
16
+ * CardView - A reusable card component with shadow
17
+ *
18
+ * @param children - The content to display inside the card
19
+ * @param style - Additional styles to apply to the card container
20
+ * @param cardPadding - Padding inside the card (default: 16)
21
+ * @param borderRadius - Border radius of the card (default: 20)
22
+ * @param backgroundColor - Background color of the card (default: white)
23
+ * @param elevation - Shadow elevation for Android (default: 4)
24
+ * @param shadowOpacity - Shadow opacity for iOS (default: 0.2)
25
+ */
26
+ const CardView: React.FC<CardViewProps> = ({
27
+ children,
28
+ style,
29
+ cardPadding = 16,
30
+ borderRadius = 20,
31
+ backgroundColor = Colors.whiteColor,
32
+ elevation = 4,
33
+ shadowOpacity = 0.2,
34
+ }) => {
35
+ return (
36
+ <View
37
+ style={[
38
+ styles.container,
39
+ {
40
+ padding: cardPadding,
41
+ borderRadius: borderRadius,
42
+ backgroundColor: backgroundColor,
43
+ shadowColor: Colors.shadowColor,
44
+ shadowOffset: { width: 0, height: 2 },
45
+ // Shadow for iOS
46
+ shadowOpacity: shadowOpacity,
47
+ // Shadow for Android
48
+ elevation: Platform.OS === 'android' ? elevation : 0,
49
+ // On Android, add a subtle border to enhance the shadow appearance
50
+ // Cross-platform shadow styling
51
+ ...Platform.select({
52
+ ios: {
53
+ shadowColor: Colors.shadowColor,
54
+ shadowOffset: { width: 0, height: 0 }, // Changed to 0,0 for all-around shadow
55
+ shadowOpacity: 0.1,
56
+ shadowRadius: 15, // Increased radius for better top shadow
57
+ borderTopWidth: 0,
58
+ },
59
+ android: {
60
+ elevation: 8,
61
+ shadowColor: Colors.shadowColor,
62
+ shadowOffset: { width: 0, height: 0 }, // All-around shadow
63
+ shadowOpacity: 0.1,
64
+ shadowRadius: 10,
65
+ borderWidth: 0.5,
66
+ borderColor: "rgba(0,0,0,0.1)",
67
+
68
+ },
69
+ web: {
70
+ shadowColor: Colors.shadowColor,
71
+ shadowOffset: { width: 0, height: 0 },
72
+ shadowOpacity: 0.1,
73
+ shadowRadius: 15,
74
+ borderTopWidth: 0,
75
+ boxShadow: '0px 0px 20px rgba(0, 0, 0, 0.1)',
76
+ },
77
+ }),
78
+ },
79
+ style,
80
+ ]}
81
+ >
82
+ {children}
83
+ </View>
84
+ );
85
+ };
86
+
87
+ const styles = StyleSheet.create({
88
+ container: {
89
+ // we don't need to set width 100% and align item to start
90
+ // width: '100%',
91
+ // alignItems: 'flex-start',
92
+ backgroundColor: Colors.whiteColor,
93
+ // Shadow properties for iOS
94
+ shadowColor: Colors.shadowColor,
95
+ shadowOffset: { width: 0, height: 2 },
96
+ shadowRadius: 4,
97
+ },
98
+ });
99
+
100
+ export default CardView;
101
+
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { View, TouchableOpacity, StyleSheet, ImageSourcePropType } from 'react-native';
3
+ import { Ionicons } from '@expo/vector-icons';
4
+ import Label from '../typography/Label';
5
+ import { TextSize, TextWeight } from '../utils/TextConstants';
6
+ import { Colors } from '../utils/BaseStyle';
7
+
8
+ interface PropertyHeaderComponentProps {
9
+ /**
10
+ * The logo to display in the header
11
+ */
12
+ logo?: ImageSourcePropType;
13
+
14
+ /**
15
+ * The title text to display in the header
16
+ */
17
+ title: string;
18
+
19
+ /**
20
+ * Function to call when the back button is pressed
21
+ */
22
+ onBackPress: () => void;
23
+
24
+ /**
25
+ * Optional custom style for the header container
26
+ */
27
+ containerStyle?: object;
28
+
29
+ /**
30
+ * Optional custom style for the logo container
31
+ */
32
+ logoContainerStyle?: object;
33
+
34
+ /**
35
+ * Optional custom style for the title
36
+ */
37
+ titleStyle?: object;
38
+ }
39
+
40
+ /**
41
+ * A reusable header component for property screens
42
+ * Displays a back button, logo, and title
43
+ */
44
+ const PropertyHeaderComponent: React.FC<PropertyHeaderComponentProps> = ({
45
+ title,
46
+ onBackPress,
47
+ containerStyle,
48
+ titleStyle,
49
+ }) => {
50
+
51
+ const styles = headerStyles();
52
+
53
+ return (
54
+ <View style={[styles.container, containerStyle]}>
55
+ {/* Back Button */}
56
+ <TouchableOpacity style={styles.backButton} onPress={onBackPress}>
57
+ <Ionicons name="arrow-back" size={24} color={Colors.secondaryTextColor} />
58
+ </TouchableOpacity>
59
+
60
+ <View style={{ justifyContent: "center", alignContent: "center" }}>
61
+ {/* Title */}
62
+ <Label
63
+ text={title}
64
+ size={TextSize.XXXXLARGE}
65
+ weight={TextWeight.THIN}
66
+ textColorType="secondary"
67
+ customStyle={[styles.title, titleStyle]}
68
+ />
69
+ </View>
70
+ </View>
71
+ );
72
+ };
73
+
74
+ const headerStyles = () => StyleSheet.create({
75
+ container: {
76
+ paddingHorizontal: 24,
77
+ paddingVertical: 16
78
+ },
79
+ backButton: {
80
+ marginBottom: 10
81
+ },
82
+ subcontainer: {
83
+ flexDirection: 'row',
84
+ justifyContent: 'center',
85
+ alignContent: 'center',
86
+ marginTop: 16
87
+ },
88
+ logoContainer: {
89
+ width: 120,
90
+ height: 60,
91
+ borderWidth: 1,
92
+ borderColor: '#ddd',
93
+ borderRadius: 8,
94
+ justifyContent: 'center',
95
+ alignItems: 'center',
96
+ },
97
+ logo: {
98
+ width: '100%',
99
+ height: '100%',
100
+ },
101
+ placeholderText: {
102
+ textAlign: 'center',
103
+ },
104
+ title: {
105
+ textAlign: "right",
106
+ paddingVertical: 8
107
+ },
108
+ });
109
+
110
+ export default PropertyHeaderComponent;