@teamnhz/rn-ui-toolkit 1.1.7 → 1.1.8

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.
@@ -1,17 +1,2 @@
1
- type AppHeaderProps = {
2
- navigation: any;
3
- headerTitle?: string;
4
- titleStyle?: object;
5
- leftImage?: any;
6
- onPressLeft?: () => void;
7
- leftIconStyle?: object;
8
- rightImageOne?: any;
9
- onPressRightOne?: () => void;
10
- rightImageOneStyle?: object;
11
- rightImageTwo?: any;
12
- onPressRightTwo?: () => void;
13
- rightImageTwoStyle?: object;
14
- headerStyle?: object;
15
- };
16
- declare const AppHeader: (props: AppHeaderProps) => void;
1
+ declare const AppHeader: (props: any) => any;
17
2
  export default AppHeader;
@@ -1,45 +1,66 @@
1
- import { View, TouchableOpacity, StyleSheet } from "react-native";
2
1
  import React from "react";
2
+ import { View, TouchableOpacity, StyleSheet, Platform, StatusBar, } from "react-native";
3
3
  import { Colors } from "../../styles";
4
4
  import { Image, T } from "../index";
5
+ const getSafeAreaTop = () => {
6
+ if (Platform.OS === "android") {
7
+ return StatusBar.currentHeight || 0;
8
+ }
9
+ return 20; // iOS safe area fallback
10
+ };
5
11
  const AppHeader = (props) => {
6
12
  const { navigation, headerTitle, titleStyle, leftImage, onPressLeft, leftIconStyle, rightImageOne, onPressRightOne, rightImageOneStyle, rightImageTwo, onPressRightTwo, rightImageTwoStyle, headerStyle, } = props;
13
+ const safeTop = getSafeAreaTop();
7
14
  navigation.setOptions({
15
+ headerLeftContainerStyle: {
16
+ paddingLeft: 12,
17
+ paddingBottom: Platform.OS === "ios" ? 4 : 0,
18
+ },
19
+ headerRightContainerStyle: {
20
+ paddingRight: 12,
21
+ paddingBottom: Platform.OS === "ios" ? 4 : 0,
22
+ },
23
+ headerTitleContainerStyle: {
24
+ paddingBottom: Platform.OS === "ios" ? 4 : 0,
25
+ },
26
+ headerStyle: [
27
+ styles.headerBase,
28
+ { paddingTop: safeTop },
29
+ headerStyle,
30
+ ],
8
31
  headerLeft: () => leftImage ? (React.createElement(TouchableOpacity, { onPress: onPressLeft, activeOpacity: 0.8 },
9
- React.createElement(Image, { source: leftImage, resizeMode: "contain", style: [styles.leftImage, leftIconStyle] }))) : null,
32
+ React.createElement(Image, { source: leftImage, resizeMode: "contain", style: [styles.icon, leftIconStyle] }))) : null,
10
33
  headerTitle: () => headerTitle ? (React.createElement(T, { title: headerTitle, style: [styles.headerTitleText, titleStyle] })) : null,
11
34
  headerRight: () => (React.createElement(View, { style: styles.headerRightWrapper },
12
35
  rightImageOne ? (React.createElement(TouchableOpacity, { onPress: onPressRightOne, activeOpacity: 0.8 },
13
- React.createElement(Image, { source: rightImageOne, resizeMode: "contain", style: [
14
- styles.rightImage,
15
- rightImageOneStyle,
16
- { marginRight: 10 },
17
- ] }))) : null,
36
+ React.createElement(Image, { source: rightImageOne, resizeMode: "contain", style: [styles.icon, rightImageOneStyle] }))) : null,
18
37
  rightImageTwo ? (React.createElement(TouchableOpacity, { onPress: onPressRightTwo, activeOpacity: 0.8 },
19
- React.createElement(Image, { source: rightImageTwo, resizeMode: "contain", style: [styles.rightImage, rightImageTwoStyle] }))) : null)),
20
- headerStyle: [styles.headerBase, headerStyle],
38
+ React.createElement(Image, { source: rightImageTwo, resizeMode: "contain", style: [styles.icon, rightImageTwoStyle] }))) : null)),
21
39
  });
40
+ return null;
22
41
  };
23
42
  export default AppHeader;
24
43
  const styles = StyleSheet.create({
25
44
  headerBase: {
26
- height: 100,
27
- width: "100%",
28
- },
29
- leftImage: {
30
- height: 44,
31
- width: 44,
45
+ backgroundColor: Colors.white,
46
+ borderBottomWidth: 1,
47
+ borderBottomColor: Colors.lightGrey,
48
+ height: Platform.OS === "ios" ? 90 : 70,
49
+ justifyContent: "center",
32
50
  },
33
- rightImage: {
34
- height: 44,
35
- width: 44,
51
+ icon: {
52
+ height: 28,
53
+ width: 28,
36
54
  },
37
55
  headerRightWrapper: {
38
56
  flexDirection: "row",
39
57
  alignItems: "center",
58
+ gap: 12,
40
59
  },
41
60
  headerTitleText: {
42
61
  color: Colors.black,
43
62
  textAlign: "center",
63
+ fontSize: 18,
64
+ fontWeight: "600",
44
65
  },
45
66
  });
@@ -2,17 +2,10 @@ import React from "react";
2
2
  type Props = {
3
3
  mediaType: "photo" | "video";
4
4
  isMultiSelect?: boolean;
5
- onSuccess: (data: any) => void;
5
+ onSuccess: (response: any) => void;
6
6
  visible: boolean;
7
7
  onClose: () => void;
8
8
  enableCompression?: boolean;
9
- imageCompressionOptions?: {
10
- maxWidth?: number;
11
- quality?: number;
12
- };
13
- videoCompressionOptions?: {
14
- compressionMethod?: "auto" | "manual";
15
- };
16
9
  };
17
10
  declare const ImagePicker: React.FC<Props>;
18
11
  export default ImagePicker;
@@ -1,162 +1,3 @@
1
- // // import React, { useCallback } from "react";
2
- // // import {
3
- // // View,
4
- // // Text,
5
- // // TouchableOpacity,
6
- // // StyleSheet,
7
- // // Image,
8
- // // Alert,
9
- // // ViewStyle,
10
- // // TextStyle,
11
- // // ImageStyle,
12
- // // } from "react-native";
13
- // // import { BottomSheet, Dividers } from "../index";
14
- // // import { launchCamera, launchImageLibrary } from "react-native-image-picker";
15
- // // import ImageCropPicker from "react-native-image-crop-picker";
16
- // // import { Colors, Images, Scale, Typography } from "../../styles";
17
- // // type StyleProps = {
18
- // // containerStyle?: ViewStyle;
19
- // // rowStyle?: ViewStyle;
20
- // // textStyle?: TextStyle;
21
- // // iconStyle?: ImageStyle;
22
- // // };
23
- // // type Props = {
24
- // // mediaType: "photo" | "video";
25
- // // isMultiSelect?: boolean;
26
- // // onSuccess: (data: any) => void;
27
- // // visible: boolean;
28
- // // onClose: () => void;
29
- // // } & StyleProps;
30
- // // const ImagePicker: React.FC<Props> = ({
31
- // // mediaType,
32
- // // isMultiSelect = false,
33
- // // onSuccess,
34
- // // visible,
35
- // // onClose,
36
- // // containerStyle,
37
- // // rowStyle,
38
- // // textStyle,
39
- // // iconStyle,
40
- // // }) => {
41
- // // const onComplete = useCallback(
42
- // // (data: any) => {
43
- // // onSuccess(data);
44
- // // onClose();
45
- // // },
46
- // // [onSuccess, onClose]
47
- // // );
48
- // // const onCamera = () => {
49
- // // if (mediaType === "photo") {
50
- // // if (isMultiSelect) {
51
- // // ImageCropPicker.openCamera({ mediaType: "photo" })
52
- // // .then((response) => onComplete([response]))
53
- // // .catch(() => {});
54
- // // } else {
55
- // // ImageCropPicker.openCamera({ mediaType: "photo" })
56
- // // .then((response) =>
57
- // // ImageCropPicker.openCropper({
58
- // // path: response?.path,
59
- // // width: response?.width,
60
- // // height: response?.height,
61
- // // mediaType: "photo",
62
- // // freeStyleCropEnabled: true,
63
- // // }).then(onComplete)
64
- // // )
65
- // // .catch(() => {});
66
- // // }
67
- // // } else {
68
- // // launchCamera({ mediaType: "video" }, (response) => {
69
- // // if (response?.assets) {
70
- // // onComplete({
71
- // // path: response.assets[0]?.uri,
72
- // // duration: response.assets[0]?.duration,
73
- // // });
74
- // // }
75
- // // });
76
- // // }
77
- // // };
78
- // // const onGallery = () => {
79
- // // if (mediaType === "photo") {
80
- // // if (isMultiSelect) {
81
- // // ImageCropPicker.openPicker({
82
- // // mediaType: "photo",
83
- // // multiple: true,
84
- // // maxFiles: 5,
85
- // // })
86
- // // .then((images) => {
87
- // // if (images.length > 5) {
88
- // // Alert.alert(
89
- // // "Limit Exceeded",
90
- // // "You can only select up to 5 images."
91
- // // );
92
- // // } else {
93
- // // onComplete(images);
94
- // // }
95
- // // })
96
- // // .catch(() => {});
97
- // // } else {
98
- // // ImageCropPicker.openPicker({ mediaType: "photo" })
99
- // // .then((response) =>
100
- // // ImageCropPicker.openCropper({
101
- // // path: response?.path,
102
- // // width: response?.width,
103
- // // height: response?.height,
104
- // // mediaType: "photo",
105
- // // freeStyleCropEnabled: true,
106
- // // }).then(onComplete)
107
- // // )
108
- // // .catch(() => {});
109
- // // }
110
- // // } else {
111
- // // launchImageLibrary({ mediaType: "video" }, (result) => {
112
- // // if (result?.assets) {
113
- // // onComplete({
114
- // // path: result.assets[0]?.uri,
115
- // // duration: result.assets[0]?.duration,
116
- // // });
117
- // // }
118
- // // });
119
- // // }
120
- // // };
121
- // // return (
122
- // // <BottomSheet visible={visible} onClose={onClose} height={230}>
123
- // // <View style={[styles.container, containerStyle]}>
124
- // // {/* Camera */}
125
- // // <TouchableOpacity style={[styles.row, rowStyle]} onPress={onCamera}>
126
- // // <Image source={Images.video_icon} style={[styles.icon, iconStyle]} />
127
- // // <Text style={[styles.text, textStyle]}>Camera</Text>
128
- // // </TouchableOpacity>
129
- // // <Dividers small />
130
- // // {/* Gallery */}
131
- // // <TouchableOpacity style={[styles.row, rowStyle]} onPress={onGallery}>
132
- // // <Image source={Images.image_icon} style={[styles.icon, iconStyle]} />
133
- // // <Text style={[styles.text, textStyle]}>Gallery</Text>
134
- // // </TouchableOpacity>
135
- // // <Dividers small />
136
- // // {/* Cancel */}
137
- // // <TouchableOpacity style={[styles.row, rowStyle]} onPress={onClose}>
138
- // // <Image source={Images.cancel} style={[styles.icon, iconStyle]} />
139
- // // <Text style={[styles.text, textStyle]}>Cancel</Text>
140
- // // </TouchableOpacity>
141
- // // </View>
142
- // // </BottomSheet>
143
- // // );
144
- // // };
145
- // // export default ImagePicker;
146
- // // const styles = StyleSheet.create({
147
- // // container: { flex: 1, padding: 16 },
148
- // // row: {
149
- // // flexDirection: "row",
150
- // // alignItems: "center",
151
- // // },
152
- // // text: { ...Typography.style.standardU(), color: Colors.white },
153
- // // icon: {
154
- // // width: Scale.moderateScale(20),
155
- // // height: Scale.moderateScale(20),
156
- // // marginRight: 10,
157
- // // tintColor: Colors.white,
158
- // // },
159
- // // });
160
1
  // import React, { useCallback } from "react";
161
2
  // import {
162
3
  // View,
@@ -169,18 +10,33 @@
169
10
  // import { BottomSheet, Dividers } from "../index";
170
11
  // import { launchCamera, launchImageLibrary } from "react-native-image-picker";
171
12
  // import ImageCropPicker from "react-native-image-crop-picker";
13
+ // import {
14
+ // Image as ImageCompressor,
15
+ // Video as VideoCompressor,
16
+ // } from "react-native-compressor";
17
+ // import RNFS from "react-native-fs";
172
18
  // import { Colors, Images, Scale, Typography } from "../../styles";
173
- // // 👇 Import your permission utilities
19
+ // // Import your permission utilities
174
20
  // import {
175
21
  // cameraPermissions,
176
22
  // galleryPermissions,
177
- // } from "../../utils/permissions"; // ✅ Make sure path is correct
23
+ // checkMicroPhonePermission,
24
+ // } from "../../utils/permissions";
178
25
  // type Props = {
179
26
  // mediaType: "photo" | "video";
180
27
  // isMultiSelect?: boolean;
181
28
  // onSuccess: (data: any) => void;
182
29
  // visible: boolean;
183
30
  // onClose: () => void;
31
+ // // Compression options
32
+ // enableCompression?: boolean;
33
+ // imageCompressionOptions?: {
34
+ // maxWidth?: number;
35
+ // quality?: number;
36
+ // };
37
+ // videoCompressionOptions?: {
38
+ // compressionMethod?: "auto" | "manual";
39
+ // };
184
40
  // };
185
41
  // const ImagePicker: React.FC<Props> = ({
186
42
  // mediaType,
@@ -188,8 +44,11 @@
188
44
  // onSuccess,
189
45
  // visible,
190
46
  // onClose,
47
+ // enableCompression = true,
48
+ // imageCompressionOptions = { maxWidth: 1080, quality: 0.7 },
49
+ // videoCompressionOptions = { compressionMethod: "auto" },
191
50
  // }) => {
192
- // // Success handler
51
+ // // Success handler
193
52
  // const onComplete = useCallback(
194
53
  // (data: any) => {
195
54
  // onSuccess(data);
@@ -197,106 +56,181 @@
197
56
  // },
198
57
  // [onSuccess, onClose]
199
58
  // );
200
- // // CAMERA HANDLER with permission check
59
+ // // Helper: Compress Image
60
+ // const compressImage = async (imagePath: string) => {
61
+ // if (!enableCompression) return imagePath;
62
+ // try {
63
+ // const compressedImage = await ImageCompressor.compress(imagePath, {
64
+ // maxWidth: imageCompressionOptions.maxWidth,
65
+ // quality: imageCompressionOptions.quality,
66
+ // });
67
+ // // Log compressed size (optional)
68
+ // const stats = await RNFS.stat(compressedImage.replace("file://", ""));
69
+ // const sizeInMB = stats.size / (1024 * 1024);
70
+ // console.log("Compressed Image Size (MB):", sizeInMB);
71
+ // return compressedImage;
72
+ // } catch (error) {
73
+ // console.error("Image compression error:", error);
74
+ // return imagePath; // Return original if compression fails
75
+ // }
76
+ // };
77
+ // // Helper: Compress Video
78
+ // const compressVideo = async (videoUri: string) => {
79
+ // if (!enableCompression) return videoUri;
80
+ // try {
81
+ // const compressedVideo = await VideoCompressor.compress(videoUri, {
82
+ // compressionMethod: videoCompressionOptions.compressionMethod,
83
+ // });
84
+ // // Log compressed size (optional)
85
+ // const stats = await RNFS.stat(compressedVideo.replace("file://", ""));
86
+ // const sizeInMB = stats.size / (1024 * 1024);
87
+ // console.log("Compressed Video Size (MB):", sizeInMB);
88
+ // return compressedVideo;
89
+ // } catch (error) {
90
+ // console.error("Video compression error:", error);
91
+ // return videoUri; // Return original if compression fails
92
+ // }
93
+ // };
94
+ // // CAMERA HANDLER with permission check and compression
201
95
  // const handleCamera = async () => {
202
96
  // await cameraPermissions(async (granted: boolean) => {
203
- // if (!granted) return; // ❌ Permission denied, handled by your alert
97
+ // if (!granted) return;
204
98
  // if (mediaType === "photo") {
205
- // if (isMultiSelect) {
206
- // // single capture, multiple not supported directly from camera
207
- // ImageCropPicker.openCamera({ mediaType: "photo" })
208
- // .then((response) => onComplete([response]))
209
- // .catch(() => {});
210
- // } else {
211
- // ImageCropPicker.openCamera({ mediaType: "photo" })
212
- // .then((response) =>
213
- // ImageCropPicker.openCropper({
214
- // path: response?.path,
215
- // width: response?.width,
216
- // height: response?.height,
217
- // mediaType: "photo",
218
- // freeStyleCropEnabled: true,
219
- // }).then(onComplete)
220
- // )
221
- // .catch(() => {});
99
+ // try {
100
+ // const response = await ImageCropPicker.openCamera({
101
+ // mediaType: "photo",
102
+ // });
103
+ // if (isMultiSelect) {
104
+ // // Single capture for multi-select mode
105
+ // const compressedPath = await compressImage(response.path);
106
+ // onComplete([{ ...response, path: compressedPath }]);
107
+ // } else {
108
+ // // Crop and compress single image
109
+ // const cropped = await ImageCropPicker.openCropper({
110
+ // path: response?.path,
111
+ // width: response?.width,
112
+ // height: response?.height,
113
+ // mediaType: "photo",
114
+ // freeStyleCropEnabled: true,
115
+ // });
116
+ // const compressedPath = await compressImage(cropped.path);
117
+ // onComplete({ ...cropped, path: compressedPath });
118
+ // }
119
+ // } catch (error) {
120
+ // console.log("Camera cancelled or error:", error);
222
121
  // }
223
122
  // } else {
224
- // // 🎥 VIDEO CAPTURE
225
- // launchCamera({ mediaType: "video" }, (response) => {
226
- // if (response?.assets?.length) {
227
- // onComplete({
228
- // path: response.assets[0]?.uri,
229
- // duration: response.assets[0]?.duration,
230
- // });
123
+ // // VIDEO CAPTURE - check microphone permission first
124
+ // const micPermission = await checkMicroPhonePermission();
125
+ // if (!micPermission) {
126
+ // Alert.alert(
127
+ // "Microphone Permission Required",
128
+ // "Please enable microphone access to record videos."
129
+ // );
130
+ // return;
131
+ // }
132
+ // launchCamera(
133
+ // {
134
+ // mediaType: "video",
135
+ // durationLimit: 60,
136
+ // videoQuality: "high",
137
+ // },
138
+ // async (response) => {
139
+ // if (response?.assets?.length) {
140
+ // const videoUri = response.assets[0]?.uri;
141
+ // const compressedPath = await compressVideo(videoUri);
142
+ // onComplete({
143
+ // path: compressedPath,
144
+ // duration: response.assets[0]?.duration,
145
+ // });
146
+ // }
231
147
  // }
232
- // });
148
+ // );
233
149
  // }
234
150
  // });
235
151
  // };
236
- // // GALLERY HANDLER with permission check
152
+ // // GALLERY HANDLER with permission check and compression
237
153
  // const handleGallery = async () => {
238
154
  // await galleryPermissions(async (granted: boolean) => {
239
- // if (!granted) return; // ❌ Permission denied, handled by your alert
155
+ // if (!granted) return;
240
156
  // if (mediaType === "photo") {
241
- // if (isMultiSelect) {
242
- // ImageCropPicker.openPicker({
243
- // mediaType: "photo",
244
- // multiple: true,
245
- // maxFiles: 5,
246
- // })
247
- // .then((images) => {
248
- // if (images.length > 5) {
249
- // Alert.alert(
250
- // "Limit Exceeded",
251
- // "You can only select up to 5 images."
252
- // );
253
- // } else {
254
- // onComplete(images);
255
- // }
256
- // })
257
- // .catch(() => {});
258
- // } else {
259
- // ImageCropPicker.openPicker({ mediaType: "photo" })
260
- // .then((response) =>
261
- // ImageCropPicker.openCropper({
262
- // path: response?.path,
263
- // width: response?.width,
264
- // height: response?.height,
265
- // mediaType: "photo",
266
- // freeStyleCropEnabled: true,
267
- // }).then(onComplete)
268
- // )
269
- // .catch(() => {});
157
+ // try {
158
+ // if (isMultiSelect) {
159
+ // const images = await ImageCropPicker.openPicker({
160
+ // mediaType: "photo",
161
+ // multiple: true,
162
+ // maxFiles: 5,
163
+ // });
164
+ // if (images.length > 5) {
165
+ // Alert.alert(
166
+ // "Limit Exceeded",
167
+ // "You can only select up to 5 images."
168
+ // );
169
+ // return;
170
+ // }
171
+ // // Compress multiple images
172
+ // const compressedImages = await Promise.all(
173
+ // images.map(async (img) => ({
174
+ // ...img,
175
+ // path: await compressImage(img.path),
176
+ // }))
177
+ // );
178
+ // onComplete(compressedImages);
179
+ // } else {
180
+ // // Single image with crop
181
+ // const response = await ImageCropPicker.openPicker({
182
+ // mediaType: "photo",
183
+ // });
184
+ // const cropped = await ImageCropPicker.openCropper({
185
+ // path: response?.path,
186
+ // width: response?.width,
187
+ // height: response?.height,
188
+ // mediaType: "photo",
189
+ // freeStyleCropEnabled: true,
190
+ // });
191
+ // const compressedPath = await compressImage(cropped.path);
192
+ // onComplete({ ...cropped, path: compressedPath });
193
+ // }
194
+ // } catch (error) {
195
+ // console.log("Gallery cancelled or error:", error);
270
196
  // }
271
197
  // } else {
272
- // // 🎥 VIDEO FROM GALLERY
273
- // launchImageLibrary({ mediaType: "video" }, (result) => {
274
- // if (result?.assets?.length) {
275
- // onComplete({
276
- // path: result.assets[0]?.uri,
277
- // duration: result.assets[0]?.duration,
278
- // });
198
+ // // VIDEO FROM GALLERY
199
+ // launchImageLibrary(
200
+ // {
201
+ // mediaType: "video",
202
+ // assetRepresentationMode: "current",
203
+ // },
204
+ // async (result) => {
205
+ // if (result?.assets?.length) {
206
+ // const videoUri = result.assets[0]?.uri;
207
+ // const compressedPath = await compressVideo(videoUri);
208
+ // onComplete({
209
+ // path: compressedPath,
210
+ // duration: result.assets[0]?.duration,
211
+ // });
212
+ // }
279
213
  // }
280
- // });
214
+ // );
281
215
  // }
282
216
  // });
283
217
  // };
284
218
  // return (
285
219
  // <BottomSheet visible={visible} onClose={onClose} height={230}>
286
220
  // <View style={styles.container}>
287
- // {/* 📸 Camera */}
221
+ // {/* Camera */}
288
222
  // <TouchableOpacity style={styles.row} onPress={handleCamera}>
289
223
  // <Image source={Images.video_icon} style={styles.icon} />
290
224
  // <Text style={styles.text}>Camera</Text>
291
225
  // </TouchableOpacity>
292
226
  // <Dividers small />
293
- // {/* 🖼️ Gallery */}
227
+ // {/* Gallery */}
294
228
  // <TouchableOpacity style={styles.row} onPress={handleGallery}>
295
229
  // <Image source={Images.image_icon} style={styles.icon} />
296
230
  // <Text style={styles.text}>Gallery</Text>
297
231
  // </TouchableOpacity>
298
232
  // <Dividers small />
299
- // {/* Cancel */}
233
+ // {/* Cancel */}
300
234
  // <TouchableOpacity style={styles.row} onPress={onClose}>
301
235
  // <Image source={Images.cancel} style={styles.icon} />
302
236
  // <Text style={styles.text}>Cancel</Text>
@@ -320,175 +254,121 @@
320
254
  // tintColor: Colors.white,
321
255
  // },
322
256
  // });
323
- import React, { useCallback } from "react";
324
- import { View, Text, TouchableOpacity, StyleSheet, Image, Alert, } from "react-native";
257
+ import React from "react";
258
+ import { View, Text, TouchableOpacity, StyleSheet, Image, } from "react-native";
325
259
  import { BottomSheet, Dividers } from "../index";
326
260
  import { launchCamera, launchImageLibrary } from "react-native-image-picker";
327
261
  import ImageCropPicker from "react-native-image-crop-picker";
328
262
  import { Image as ImageCompressor, Video as VideoCompressor, } from "react-native-compressor";
329
- import RNFS from "react-native-fs";
330
263
  import { Colors, Images, Scale, Typography } from "../../styles";
331
- // Import your permission utilities
332
264
  import { cameraPermissions, galleryPermissions, checkMicroPhonePermission, } from "../../utils/permissions";
333
- const ImagePicker = ({ mediaType, isMultiSelect = false, onSuccess, visible, onClose, enableCompression = true, imageCompressionOptions = { maxWidth: 1080, quality: 0.7 }, videoCompressionOptions = { compressionMethod: "auto" }, }) => {
334
- // Success handler
335
- const onComplete = useCallback((data) => {
336
- onSuccess(data);
265
+ const ImagePicker = ({ mediaType, isMultiSelect = false, onSuccess, visible, onClose, enableCompression = true, }) => {
266
+ // Close modal + start loader in parent
267
+ const sendRawAndClose = (rawData) => {
337
268
  onClose();
338
- }, [onSuccess, onClose]);
339
- // Helper: Compress Image
340
- const compressImage = async (imagePath) => {
341
- if (!enableCompression)
342
- return imagePath;
269
+ onSuccess({ loading: true, data: rawData });
270
+ };
271
+ // After compression finished
272
+ const sendFinalCompressed = (compressed) => {
273
+ onSuccess({ loading: false, data: compressed });
274
+ };
275
+ const compressImage = async (path) => {
343
276
  try {
344
- const compressedImage = await ImageCompressor.compress(imagePath, {
345
- maxWidth: imageCompressionOptions.maxWidth,
346
- quality: imageCompressionOptions.quality,
277
+ const compressed = await ImageCompressor.compress(path, {
278
+ maxWidth: 1080,
279
+ quality: 0.7,
347
280
  });
348
- // Log compressed size (optional)
349
- const stats = await RNFS.stat(compressedImage.replace("file://", ""));
350
- const sizeInMB = stats.size / (1024 * 1024);
351
- console.log("Compressed Image Size (MB):", sizeInMB);
352
- return compressedImage;
281
+ return compressed;
353
282
  }
354
- catch (error) {
355
- console.error("Image compression error:", error);
356
- return imagePath; // Return original if compression fails
283
+ catch (e) {
284
+ return path;
357
285
  }
358
286
  };
359
- // Helper: Compress Video
360
- const compressVideo = async (videoUri) => {
361
- if (!enableCompression)
362
- return videoUri;
287
+ const compressVideo = async (uri) => {
363
288
  try {
364
- const compressedVideo = await VideoCompressor.compress(videoUri, {
365
- compressionMethod: videoCompressionOptions.compressionMethod,
289
+ const compressed = await VideoCompressor.compress(uri, {
290
+ compressionMethod: "auto",
366
291
  });
367
- // Log compressed size (optional)
368
- const stats = await RNFS.stat(compressedVideo.replace("file://", ""));
369
- const sizeInMB = stats.size / (1024 * 1024);
370
- console.log("Compressed Video Size (MB):", sizeInMB);
371
- return compressedVideo;
292
+ return compressed;
372
293
  }
373
- catch (error) {
374
- console.error("Video compression error:", error);
375
- return videoUri; // Return original if compression fails
294
+ catch (e) {
295
+ return uri;
376
296
  }
377
297
  };
378
- // CAMERA HANDLER with permission check and compression
298
+ // ---------- CAMERA ----------
379
299
  const handleCamera = async () => {
380
300
  await cameraPermissions(async (granted) => {
381
301
  if (!granted)
382
302
  return;
383
303
  if (mediaType === "photo") {
384
304
  try {
385
- const response = await ImageCropPicker.openCamera({
305
+ const picked = await ImageCropPicker.openCamera({
386
306
  mediaType: "photo",
387
307
  });
388
- if (isMultiSelect) {
389
- // Single capture for multi-select mode
390
- const compressedPath = await compressImage(response.path);
391
- onComplete([{ ...response, path: compressedPath }]);
392
- }
393
- else {
394
- // Crop and compress single image
395
- const cropped = await ImageCropPicker.openCropper({
396
- path: response?.path,
397
- width: response?.width,
398
- height: response?.height,
399
- mediaType: "photo",
400
- freeStyleCropEnabled: true,
401
- });
402
- const compressedPath = await compressImage(cropped.path);
403
- onComplete({ ...cropped, path: compressedPath });
404
- }
308
+ sendRawAndClose(picked); // CLOSE MODAL + START LOADER
309
+ setTimeout(async () => {
310
+ const compressedPath = await compressImage(picked.path);
311
+ sendFinalCompressed({ ...picked, path: compressedPath });
312
+ }, 10);
405
313
  }
406
- catch (error) {
407
- console.log("Camera cancelled or error:", error);
314
+ catch (err) {
315
+ console.log(err);
408
316
  }
409
317
  }
410
318
  else {
411
- // VIDEO CAPTURE - check microphone permission first
412
- const micPermission = await checkMicroPhonePermission();
413
- if (!micPermission) {
414
- Alert.alert("Microphone Permission Required", "Please enable microphone access to record videos.");
319
+ // VIDEO
320
+ const mic = await checkMicroPhonePermission();
321
+ if (!mic)
415
322
  return;
416
- }
417
- launchCamera({
418
- mediaType: "video",
419
- durationLimit: 60,
420
- videoQuality: "high",
421
- }, async (response) => {
422
- if (response?.assets?.length) {
423
- const videoUri = response.assets[0]?.uri;
424
- const compressedPath = await compressVideo(videoUri);
425
- onComplete({
426
- path: compressedPath,
427
- duration: response.assets[0]?.duration,
323
+ launchCamera({ mediaType: "video", durationLimit: 60, videoQuality: "high" }, (res) => {
324
+ if (!res?.assets?.length)
325
+ return;
326
+ const raw = res.assets[0];
327
+ sendRawAndClose(raw);
328
+ setTimeout(async () => {
329
+ const compressed = await compressVideo(raw.uri);
330
+ sendFinalCompressed({
331
+ path: compressed,
332
+ duration: raw.duration,
428
333
  });
429
- }
334
+ }, 10);
430
335
  });
431
336
  }
432
337
  });
433
338
  };
434
- // GALLERY HANDLER with permission check and compression
339
+ // ---------- GALLERY ----------
435
340
  const handleGallery = async () => {
436
341
  await galleryPermissions(async (granted) => {
437
342
  if (!granted)
438
343
  return;
439
344
  if (mediaType === "photo") {
440
345
  try {
441
- if (isMultiSelect) {
442
- const images = await ImageCropPicker.openPicker({
443
- mediaType: "photo",
444
- multiple: true,
445
- maxFiles: 5,
446
- });
447
- if (images.length > 5) {
448
- Alert.alert("Limit Exceeded", "You can only select up to 5 images.");
449
- return;
450
- }
451
- // Compress multiple images
452
- const compressedImages = await Promise.all(images.map(async (img) => ({
453
- ...img,
454
- path: await compressImage(img.path),
455
- })));
456
- onComplete(compressedImages);
457
- }
458
- else {
459
- // Single image with crop
460
- const response = await ImageCropPicker.openPicker({
461
- mediaType: "photo",
462
- });
463
- const cropped = await ImageCropPicker.openCropper({
464
- path: response?.path,
465
- width: response?.width,
466
- height: response?.height,
467
- mediaType: "photo",
468
- freeStyleCropEnabled: true,
469
- });
470
- const compressedPath = await compressImage(cropped.path);
471
- onComplete({ ...cropped, path: compressedPath });
472
- }
346
+ const picked = await ImageCropPicker.openPicker({
347
+ mediaType: "photo",
348
+ });
349
+ sendRawAndClose(picked);
350
+ setTimeout(async () => {
351
+ const compressed = await compressImage(picked.path);
352
+ sendFinalCompressed({ ...picked, path: compressed });
353
+ }, 10);
473
354
  }
474
- catch (error) {
475
- console.log("Gallery cancelled or error:", error);
355
+ catch (e) {
356
+ console.log(e);
476
357
  }
477
358
  }
478
359
  else {
479
- // VIDEO FROM GALLERY
480
- launchImageLibrary({
481
- mediaType: "video",
482
- assetRepresentationMode: "current",
483
- }, async (result) => {
484
- if (result?.assets?.length) {
485
- const videoUri = result.assets[0]?.uri;
486
- const compressedPath = await compressVideo(videoUri);
487
- onComplete({
488
- path: compressedPath,
489
- duration: result.assets[0]?.duration,
360
+ launchImageLibrary({ mediaType: "video" }, (res) => {
361
+ if (!res?.assets?.length)
362
+ return;
363
+ const raw = res.assets[0];
364
+ sendRawAndClose(raw);
365
+ setTimeout(async () => {
366
+ const compressed = await compressVideo(raw.uri);
367
+ sendFinalCompressed({
368
+ path: compressed,
369
+ duration: raw.duration,
490
370
  });
491
- }
371
+ }, 10);
492
372
  });
493
373
  }
494
374
  });
@@ -510,10 +390,7 @@ const ImagePicker = ({ mediaType, isMultiSelect = false, onSuccess, visible, onC
510
390
  export default ImagePicker;
511
391
  const styles = StyleSheet.create({
512
392
  container: { flex: 1, padding: 16 },
513
- row: {
514
- flexDirection: "row",
515
- alignItems: "center",
516
- },
393
+ row: { flexDirection: "row", alignItems: "center" },
517
394
  text: { ...Typography.style.standardU(), color: Colors.white },
518
395
  icon: {
519
396
  width: Scale.moderateScale(20),
@@ -35,6 +35,7 @@ interface InputProps extends TextInputProps {
35
35
  onPressCountryCode?: () => void;
36
36
  countryCodeWrapperStyle?: object;
37
37
  countryCodeTextStyle?: object;
38
+ isError?: boolean;
38
39
  }
39
40
  declare const Input: React.FC<InputProps>;
40
41
  export default Input;
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
4
4
  import { Colors, Typography, Scale, Images } from "../../styles";
5
5
  import { defaultRegex } from "../../utils/regex";
6
6
  import { messages } from "../../constants/messages";
7
- const Input = ({ intlType, textKey, placeholder, inputPlaceholderTextColor, leftIcon, rightIcon, onLeftIconPress, onRightIconPress, style, containerStyle, inputStyle, errorTextStyle, leftIconStyle, rightIconStyle, leftIconWrapperStyle, rightIconWrapperStyle, type = "text", editable = true, error, value = "", customRegex, customErrorMessage, onChangeText, onValidation, onBlur: restOnBlur, onFocus: restOnFocus, multiline = false, maxLength, returnKeyType, countryCode, onPressCountryCode, countryCodeTextStyle, countryCodeWrapperStyle, ...rest }) => {
7
+ const Input = ({ intlType, textKey, placeholder, inputPlaceholderTextColor, leftIcon, rightIcon, onLeftIconPress, onRightIconPress, style, containerStyle, inputStyle, errorTextStyle, leftIconStyle, rightIconStyle, leftIconWrapperStyle, rightIconWrapperStyle, type = "text", editable = true, error, value = "", customRegex, customErrorMessage, onChangeText, onValidation, onBlur: restOnBlur, onFocus: restOnFocus, multiline = false, maxLength, returnKeyType, countryCode, onPressCountryCode, countryCodeTextStyle, countryCodeWrapperStyle, isError = true, ...rest }) => {
8
8
  const { t } = useTranslation();
9
9
  const [secureText, setSecureText] = useState(type === "password");
10
10
  const [internalError, setInternalError] = useState(null);
@@ -97,7 +97,7 @@ const Input = ({ intlType, textKey, placeholder, inputPlaceholderTextColor, left
97
97
  type === "password" ? (React.createElement(TouchableOpacity, { onPress: () => setSecureText(!secureText), style: [styles.iconWrapper, rightIconWrapperStyle] },
98
98
  React.createElement(Image, { source: secureText ? Images.Eyeoff : Images.Eyeon, style: [styles.icon, rightIconStyle], resizeMode: "contain" }))) : (rightIcon && (React.createElement(TouchableOpacity, { onPress: onRightIconPress, disabled: !onRightIconPress, style: [styles.iconWrapper, rightIconWrapperStyle] },
99
99
  React.createElement(Image, { source: rightIcon, style: [styles.icon, rightIconStyle], resizeMode: "contain" }))))),
100
- showError && (React.createElement(Text, { style: [styles.errorText, errorTextStyle] }, showError))));
100
+ showError && isError && (React.createElement(Text, { style: [styles.errorText, errorTextStyle] }, showError))));
101
101
  };
102
102
  export default Input;
103
103
  const styles = StyleSheet.create({
@@ -167,239 +167,3 @@ const styles = StyleSheet.create({
167
167
  color: Colors.textColor,
168
168
  },
169
169
  });
170
- // import React, { useState, useCallback, useEffect } from "react";
171
- // import {
172
- // View,
173
- // TextInput,
174
- // TextInputProps,
175
- // StyleSheet,
176
- // Image,
177
- // ImageSourcePropType,
178
- // TouchableOpacity,
179
- // Text,
180
- // NativeSyntheticEvent,
181
- // TextInputFocusEventData,
182
- // } from "react-native";
183
- // import { useTranslation } from "react-i18next";
184
- // import { Colors, Typography, Scale, Images } from "../../styles";
185
- // import { defaultRegex, InputType } from "../../utils/regex";
186
- // import { messages } from "../../constants/messages";
187
- // interface InputProps extends TextInputProps {
188
- // intlType?: string;
189
- // textKey?: string;
190
- // leftIcon?: ImageSourcePropType;
191
- // rightIcon?: ImageSourcePropType;
192
- // onLeftIconPress?: () => void;
193
- // onRightIconPress?: () => void;
194
- // type?: InputType;
195
- // editable?: boolean;
196
- // error?: string | boolean;
197
- // value?: string;
198
- // customRegex?: RegExp;
199
- // customErrorMessage?: string;
200
- // onChangeText?: (text: string) => void;
201
- // onValidation?: (errorObj: { type: InputType; hasError: boolean; message?: string }) => void;
202
- // containerStyle?: object;
203
- // inputStyle?: object;
204
- // errorTextStyle?: object;
205
- // leftIconStyle?: object;
206
- // rightIconStyle?: object;
207
- // }
208
- // const Input: React.FC<InputProps> = ({
209
- // intlType,
210
- // textKey,
211
- // placeholder,
212
- // leftIcon,
213
- // rightIcon,
214
- // onLeftIconPress,
215
- // onRightIconPress,
216
- // style,
217
- // containerStyle,
218
- // inputStyle,
219
- // errorTextStyle,
220
- // leftIconStyle,
221
- // rightIconStyle,
222
- // type = "text",
223
- // editable = true,
224
- // error,
225
- // value = "",
226
- // customRegex,
227
- // customErrorMessage,
228
- // onChangeText,
229
- // onValidation,
230
- // onBlur: restOnBlur,
231
- // onFocus: restOnFocus,
232
- // multiline = false,
233
- // ...rest
234
- // }) => {
235
- // const { t } = useTranslation();
236
- // const [secureText, setSecureText] = useState(type === "password");
237
- // const [internalError, setInternalError] = useState<string | null>(null);
238
- // const [wasBlurred, setWasBlurred] = useState(false);
239
- // const [isFocused, setIsFocused] = useState(false);
240
- // const translatedPlaceholder =
241
- // intlType && textKey ? String(t(textKey, { ns: intlType, value })) : placeholder;
242
- // const keyboardType: TextInputProps["keyboardType"] =
243
- // type === "email" ? "email-address" : type === "number" ? "numeric" : "default";
244
- // const validateInternal = useCallback(
245
- // (text: string) => {
246
- // if (!wasBlurred || !editable) return;
247
- // const regexToUse = customRegex ?? defaultRegex[type];
248
- // const fallbackMsg = messages.invalid[type] || "Invalid input";
249
- // const msg = customErrorMessage || fallbackMsg;
250
- // if (text === "" || !regexToUse.test(text)) {
251
- // setInternalError(msg);
252
- // onValidation?.({ type, hasError: true, message: msg });
253
- // } else {
254
- // setInternalError(null);
255
- // onValidation?.({ type, hasError: false });
256
- // }
257
- // },
258
- // [wasBlurred, editable, type, customRegex, customErrorMessage, onValidation]
259
- // );
260
- // useEffect(() => {
261
- // if (!customRegex) return;
262
- // if (internalError && value && customRegex.test(value)) {
263
- // setInternalError(null);
264
- // onValidation?.({ type, hasError: false });
265
- // }
266
- // }, [value]);
267
- // const handleChangeText = (text: string) => {
268
- // onChangeText?.(text);
269
- // if (wasBlurred) validateInternal(text);
270
- // };
271
- // const handleFocus = (e?: NativeSyntheticEvent<TextInputFocusEventData>) => {
272
- // setIsFocused(true);
273
- // restOnFocus?.(e as any);
274
- // };
275
- // const handleBlur = (e?: NativeSyntheticEvent<TextInputFocusEventData>) => {
276
- // setIsFocused(false);
277
- // if (!wasBlurred) setWasBlurred(true);
278
- // if (editable) validateInternal(value || "");
279
- // restOnBlur?.(e as any);
280
- // };
281
- // const rawError = (typeof error === "string" ? error : undefined) ?? internalError ?? null;
282
- // const showError = (() => {
283
- // if (!rawError || !editable) return null;
284
- // return wasBlurred && !isFocused ? rawError : null;
285
- // })();
286
- // return (
287
- // <View style={{ marginBottom: 10 }}>
288
- // <View
289
- // style={[
290
- // styles.container,
291
- // !editable && styles.disabled,
292
- // !!showError && styles.errorBorder,
293
- // isFocused && styles.focusedBorder,
294
- // style,
295
- // containerStyle,
296
- // multiline && styles.multilineContainer,
297
- // ]}
298
- // >
299
- // {leftIcon && (
300
- // <TouchableOpacity
301
- // onPress={onLeftIconPress}
302
- // disabled={!onLeftIconPress}
303
- // style={styles.iconWrapper}
304
- // >
305
- // <Image source={leftIcon} style={[styles.icon, leftIconStyle]} resizeMode="contain" />
306
- // </TouchableOpacity>
307
- // )}
308
- // <TextInput
309
- // style={[
310
- // styles.input,
311
- // !editable && styles.disabledText,
312
- // multiline && styles.multilineInput,
313
- // inputStyle,
314
- // ]}
315
- // placeholder={translatedPlaceholder}
316
- // placeholderTextColor={Colors.textGrey}
317
- // editable={editable}
318
- // secureTextEntry={secureText}
319
- // keyboardType={keyboardType}
320
- // value={value}
321
- // onChangeText={handleChangeText}
322
- // onFocus={handleFocus}
323
- // onBlur={handleBlur}
324
- // multiline={multiline}
325
- // {...rest}
326
- // />
327
- // {type === "password" ? (
328
- // <TouchableOpacity onPress={() => setSecureText(!secureText)} style={styles.iconWrapper}>
329
- // <Image
330
- // source={secureText ? Images.Eyeoff : Images.Eyeon}
331
- // style={[styles.icon, rightIconStyle]}
332
- // resizeMode="contain"
333
- // />
334
- // </TouchableOpacity>
335
- // ) : (
336
- // rightIcon && (
337
- // <TouchableOpacity
338
- // onPress={onRightIconPress}
339
- // disabled={!onRightIconPress}
340
- // style={styles.iconWrapper}
341
- // >
342
- // <Image source={rightIcon} style={[styles.icon, rightIconStyle]} resizeMode="contain" />
343
- // </TouchableOpacity>
344
- // )
345
- // )}
346
- // </View>
347
- // {showError && <Text style={[styles.errorText, errorTextStyle]}>{showError}</Text>}
348
- // </View>
349
- // );
350
- // };
351
- // export default Input;
352
- // const styles = StyleSheet.create({
353
- // container: {
354
- // flexDirection: "row",
355
- // alignItems: "center",
356
- // borderRadius: 5,
357
- // borderWidth: 1,
358
- // borderColor: Colors.primaryColor,
359
- // backgroundColor: Colors.white,
360
- // height: Scale.moderateScale(50),
361
- // paddingHorizontal: Scale.moderateScale(4),
362
- // },
363
- // multilineContainer: {
364
- // height: "auto",
365
- // minHeight: Scale.moderateScale(80),
366
- // alignItems: "flex-start",
367
- // paddingVertical: Scale.moderateScale(8),
368
- // },
369
- // focusedBorder: {
370
- // borderColor: Colors.primaryColor,
371
- // },
372
- // input: {
373
- // flex: 1,
374
- // ...Typography.style.standardU(),
375
- // textTransform: "none",
376
- // color: Colors.textColor,
377
- // },
378
- // multilineInput: {
379
- // textAlignVertical: "top",
380
- // paddingTop: Scale.moderateScale(5),
381
- // },
382
- // iconWrapper: {
383
- // padding: Scale.moderateScale(5),
384
- // },
385
- // icon: {
386
- // width: Scale.moderateScale(20),
387
- // height: Scale.moderateScale(20),
388
- // tintColor: Colors.primaryColor,
389
- // },
390
- // disabled: {
391
- // backgroundColor: Colors.lightGrey,
392
- // borderColor: Colors.borderGrey,
393
- // },
394
- // disabledText: {
395
- // color: Colors.disabledGrey,
396
- // },
397
- // errorBorder: {
398
- // borderColor: Colors.dangerRed,
399
- // },
400
- // errorText: {
401
- // color: Colors.dangerRed,
402
- // fontSize: Scale.moderateScale(12),
403
- // marginTop: 3,
404
- // },
405
- // });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamnhz/rn-ui-toolkit",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [