@grupalia/rn-ui-kit 0.15.0 → 0.17.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 (197) hide show
  1. package/lib/commonjs/assets/illustrations/camera.svg +87 -0
  2. package/lib/commonjs/components/BaseDatesScrollView.js +71 -0
  3. package/lib/commonjs/components/BaseDatesScrollView.js.map +1 -0
  4. package/lib/commonjs/components/CameraImageInput.js +478 -0
  5. package/lib/commonjs/components/CameraImageInput.js.map +1 -0
  6. package/lib/commonjs/components/CameraWrapperModal.js +255 -0
  7. package/lib/commonjs/components/CameraWrapperModal.js.map +1 -0
  8. package/lib/commonjs/components/DateSelector.js +72 -0
  9. package/lib/commonjs/components/DateSelector.js.map +1 -0
  10. package/lib/commonjs/components/DateTimeSelector.js +197 -0
  11. package/lib/commonjs/components/DateTimeSelector.js.map +1 -0
  12. package/lib/commonjs/components/FormikCameraImageInput.js +37 -0
  13. package/lib/commonjs/components/FormikCameraImageInput.js.map +1 -0
  14. package/lib/commonjs/components/FormikDateSelector.js +28 -0
  15. package/lib/commonjs/components/FormikDateSelector.js.map +1 -0
  16. package/lib/commonjs/components/FormikDateTimeSelector.js +40 -0
  17. package/lib/commonjs/components/FormikDateTimeSelector.js.map +1 -0
  18. package/lib/commonjs/components/FormikDateTimeSelectorBottomSheet.js +107 -0
  19. package/lib/commonjs/components/FormikDateTimeSelectorBottomSheet.js.map +1 -0
  20. package/lib/commonjs/components/ImagePickerBottomSheet.js +100 -0
  21. package/lib/commonjs/components/ImagePickerBottomSheet.js.map +1 -0
  22. package/lib/commonjs/components/PhotoPickerModal.js +98 -0
  23. package/lib/commonjs/components/PhotoPickerModal.js.map +1 -0
  24. package/lib/commonjs/components/Toasts.js +188 -0
  25. package/lib/commonjs/components/Toasts.js.map +1 -0
  26. package/lib/commonjs/components/index.js +101 -0
  27. package/lib/commonjs/components/index.js.map +1 -1
  28. package/lib/commonjs/components/svgs/Camera.js +17 -0
  29. package/lib/commonjs/components/svgs/Camera.js.map +1 -0
  30. package/lib/commonjs/hoc-components.js +3 -1
  31. package/lib/commonjs/hoc-components.js.map +1 -1
  32. package/lib/commonjs/hooks/index.js +41 -0
  33. package/lib/commonjs/hooks/index.js.map +1 -0
  34. package/lib/commonjs/hooks/useInternetConnectionStatus.js +182 -0
  35. package/lib/commonjs/hooks/useInternetConnectionStatus.js.map +1 -0
  36. package/lib/commonjs/types/svg.d.js +2 -0
  37. package/lib/commonjs/types/svg.d.js.map +1 -0
  38. package/lib/commonjs/utils/date.js +8 -0
  39. package/lib/commonjs/utils/date.js.map +1 -0
  40. package/lib/commonjs/utils/fileDirectoryUtils.js +19 -0
  41. package/lib/commonjs/utils/fileDirectoryUtils.js.map +1 -0
  42. package/lib/commonjs/utils/index.js +22 -0
  43. package/lib/commonjs/utils/index.js.map +1 -1
  44. package/lib/commonjs/utils/timeConstants.js +15 -0
  45. package/lib/commonjs/utils/timeConstants.js.map +1 -0
  46. package/lib/module/assets/illustrations/camera.svg +87 -0
  47. package/lib/module/components/BaseDatesScrollView.js +66 -0
  48. package/lib/module/components/BaseDatesScrollView.js.map +1 -0
  49. package/lib/module/components/CameraImageInput.js +471 -0
  50. package/lib/module/components/CameraImageInput.js.map +1 -0
  51. package/lib/module/components/CameraWrapperModal.js +250 -0
  52. package/lib/module/components/CameraWrapperModal.js.map +1 -0
  53. package/lib/module/components/DateSelector.js +66 -0
  54. package/lib/module/components/DateSelector.js.map +1 -0
  55. package/lib/module/components/DateTimeSelector.js +191 -0
  56. package/lib/module/components/DateTimeSelector.js.map +1 -0
  57. package/lib/module/components/FormikCameraImageInput.js +32 -0
  58. package/lib/module/components/FormikCameraImageInput.js.map +1 -0
  59. package/lib/module/components/FormikDateSelector.js +23 -0
  60. package/lib/module/components/FormikDateSelector.js.map +1 -0
  61. package/lib/module/components/FormikDateTimeSelector.js +35 -0
  62. package/lib/module/components/FormikDateTimeSelector.js.map +1 -0
  63. package/lib/module/components/FormikDateTimeSelectorBottomSheet.js +102 -0
  64. package/lib/module/components/FormikDateTimeSelectorBottomSheet.js.map +1 -0
  65. package/lib/module/components/ImagePickerBottomSheet.js +95 -0
  66. package/lib/module/components/ImagePickerBottomSheet.js.map +1 -0
  67. package/lib/module/components/PhotoPickerModal.js +93 -0
  68. package/lib/module/components/PhotoPickerModal.js.map +1 -0
  69. package/lib/module/components/Toasts.js +182 -0
  70. package/lib/module/components/Toasts.js.map +1 -0
  71. package/lib/module/components/index.js +11 -0
  72. package/lib/module/components/index.js.map +1 -1
  73. package/lib/module/components/svgs/Camera.js +12 -0
  74. package/lib/module/components/svgs/Camera.js.map +1 -0
  75. package/lib/module/hoc-components.js +2 -0
  76. package/lib/module/hoc-components.js.map +1 -1
  77. package/lib/module/hooks/index.js +6 -0
  78. package/lib/module/hooks/index.js.map +1 -0
  79. package/lib/module/hooks/useInternetConnectionStatus.js +177 -0
  80. package/lib/module/hooks/useInternetConnectionStatus.js.map +1 -0
  81. package/lib/module/types/svg.d.js +2 -0
  82. package/lib/module/types/svg.d.js.map +1 -0
  83. package/lib/module/utils/date.js +4 -0
  84. package/lib/module/utils/date.js.map +1 -0
  85. package/lib/module/utils/fileDirectoryUtils.js +14 -0
  86. package/lib/module/utils/fileDirectoryUtils.js.map +1 -0
  87. package/lib/module/utils/index.js +2 -0
  88. package/lib/module/utils/index.js.map +1 -1
  89. package/lib/module/utils/timeConstants.js +12 -0
  90. package/lib/module/utils/timeConstants.js.map +1 -0
  91. package/lib/typescript/commonjs/components/BaseDatesScrollView.d.ts +16 -0
  92. package/lib/typescript/commonjs/components/BaseDatesScrollView.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/components/CameraImageInput.d.ts +41 -0
  94. package/lib/typescript/commonjs/components/CameraImageInput.d.ts.map +1 -0
  95. package/lib/typescript/commonjs/components/CameraWrapperModal.d.ts +18 -0
  96. package/lib/typescript/commonjs/components/CameraWrapperModal.d.ts.map +1 -0
  97. package/lib/typescript/commonjs/components/DateSelector.d.ts +22 -0
  98. package/lib/typescript/commonjs/components/DateSelector.d.ts.map +1 -0
  99. package/lib/typescript/commonjs/components/DateTimeSelector.d.ts +28 -0
  100. package/lib/typescript/commonjs/components/DateTimeSelector.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/components/FormikCameraImageInput.d.ts +14 -0
  102. package/lib/typescript/commonjs/components/FormikCameraImageInput.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/components/FormikDateSelector.d.ts +14 -0
  104. package/lib/typescript/commonjs/components/FormikDateSelector.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/components/FormikDateTimeSelector.d.ts +14 -0
  106. package/lib/typescript/commonjs/components/FormikDateTimeSelector.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/components/FormikDateTimeSelectorBottomSheet.d.ts +20 -0
  108. package/lib/typescript/commonjs/components/FormikDateTimeSelectorBottomSheet.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/components/ImagePickerBottomSheet.d.ts +15 -0
  110. package/lib/typescript/commonjs/components/ImagePickerBottomSheet.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/components/PhotoPickerModal.d.ts +19 -0
  112. package/lib/typescript/commonjs/components/PhotoPickerModal.d.ts.map +1 -0
  113. package/lib/typescript/commonjs/components/Toasts.d.ts +3 -0
  114. package/lib/typescript/commonjs/components/Toasts.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/components/index.d.ts +11 -0
  116. package/lib/typescript/commonjs/components/index.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/components/svgs/Camera.d.ts +9 -0
  118. package/lib/typescript/commonjs/components/svgs/Camera.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/hoc-components.d.ts +12 -0
  120. package/lib/typescript/commonjs/hoc-components.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/hooks/index.d.ts +4 -0
  122. package/lib/typescript/commonjs/hooks/index.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/hooks/useInternetConnectionStatus.d.ts +20 -0
  124. package/lib/typescript/commonjs/hooks/useInternetConnectionStatus.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/utils/date.d.ts +2 -0
  126. package/lib/typescript/commonjs/utils/date.d.ts.map +1 -0
  127. package/lib/typescript/commonjs/utils/fileDirectoryUtils.d.ts +3 -0
  128. package/lib/typescript/commonjs/utils/fileDirectoryUtils.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/utils/index.d.ts +2 -0
  130. package/lib/typescript/commonjs/utils/index.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/utils/timeConstants.d.ts +10 -0
  132. package/lib/typescript/commonjs/utils/timeConstants.d.ts.map +1 -0
  133. package/lib/typescript/module/components/BaseDatesScrollView.d.ts +16 -0
  134. package/lib/typescript/module/components/BaseDatesScrollView.d.ts.map +1 -0
  135. package/lib/typescript/module/components/CameraImageInput.d.ts +41 -0
  136. package/lib/typescript/module/components/CameraImageInput.d.ts.map +1 -0
  137. package/lib/typescript/module/components/CameraWrapperModal.d.ts +18 -0
  138. package/lib/typescript/module/components/CameraWrapperModal.d.ts.map +1 -0
  139. package/lib/typescript/module/components/DateSelector.d.ts +22 -0
  140. package/lib/typescript/module/components/DateSelector.d.ts.map +1 -0
  141. package/lib/typescript/module/components/DateTimeSelector.d.ts +28 -0
  142. package/lib/typescript/module/components/DateTimeSelector.d.ts.map +1 -0
  143. package/lib/typescript/module/components/FormikCameraImageInput.d.ts +14 -0
  144. package/lib/typescript/module/components/FormikCameraImageInput.d.ts.map +1 -0
  145. package/lib/typescript/module/components/FormikDateSelector.d.ts +14 -0
  146. package/lib/typescript/module/components/FormikDateSelector.d.ts.map +1 -0
  147. package/lib/typescript/module/components/FormikDateTimeSelector.d.ts +14 -0
  148. package/lib/typescript/module/components/FormikDateTimeSelector.d.ts.map +1 -0
  149. package/lib/typescript/module/components/FormikDateTimeSelectorBottomSheet.d.ts +20 -0
  150. package/lib/typescript/module/components/FormikDateTimeSelectorBottomSheet.d.ts.map +1 -0
  151. package/lib/typescript/module/components/ImagePickerBottomSheet.d.ts +15 -0
  152. package/lib/typescript/module/components/ImagePickerBottomSheet.d.ts.map +1 -0
  153. package/lib/typescript/module/components/PhotoPickerModal.d.ts +19 -0
  154. package/lib/typescript/module/components/PhotoPickerModal.d.ts.map +1 -0
  155. package/lib/typescript/module/components/Toasts.d.ts +3 -0
  156. package/lib/typescript/module/components/Toasts.d.ts.map +1 -0
  157. package/lib/typescript/module/components/index.d.ts +11 -0
  158. package/lib/typescript/module/components/index.d.ts.map +1 -1
  159. package/lib/typescript/module/components/svgs/Camera.d.ts +9 -0
  160. package/lib/typescript/module/components/svgs/Camera.d.ts.map +1 -0
  161. package/lib/typescript/module/hoc-components.d.ts +12 -0
  162. package/lib/typescript/module/hoc-components.d.ts.map +1 -1
  163. package/lib/typescript/module/hooks/index.d.ts +4 -0
  164. package/lib/typescript/module/hooks/index.d.ts.map +1 -0
  165. package/lib/typescript/module/hooks/useInternetConnectionStatus.d.ts +20 -0
  166. package/lib/typescript/module/hooks/useInternetConnectionStatus.d.ts.map +1 -0
  167. package/lib/typescript/module/utils/date.d.ts +2 -0
  168. package/lib/typescript/module/utils/date.d.ts.map +1 -0
  169. package/lib/typescript/module/utils/fileDirectoryUtils.d.ts +3 -0
  170. package/lib/typescript/module/utils/fileDirectoryUtils.d.ts.map +1 -0
  171. package/lib/typescript/module/utils/index.d.ts +2 -0
  172. package/lib/typescript/module/utils/index.d.ts.map +1 -1
  173. package/lib/typescript/module/utils/timeConstants.d.ts +10 -0
  174. package/lib/typescript/module/utils/timeConstants.d.ts.map +1 -0
  175. package/package.json +31 -4
  176. package/src/components/BaseDatesScrollView.tsx +123 -0
  177. package/src/components/CameraImageInput.tsx +610 -0
  178. package/src/components/CameraWrapperModal.tsx +309 -0
  179. package/src/components/DateSelector.tsx +91 -0
  180. package/src/components/DateTimeSelector.tsx +267 -0
  181. package/src/components/FormikCameraImageInput.tsx +39 -0
  182. package/src/components/FormikDateSelector.tsx +36 -0
  183. package/src/components/FormikDateTimeSelector.tsx +49 -0
  184. package/src/components/FormikDateTimeSelectorBottomSheet.tsx +129 -0
  185. package/src/components/ImagePickerBottomSheet.tsx +109 -0
  186. package/src/components/PhotoPickerModal.tsx +116 -0
  187. package/src/components/Toasts.tsx +200 -0
  188. package/src/components/index.ts +11 -0
  189. package/src/components/svgs/Camera.tsx +14 -0
  190. package/src/hooks/index.ts +3 -0
  191. package/src/hooks/useBreakpoints.ts +20 -0
  192. package/src/hooks/useInternetConnectionStatus.ts +221 -0
  193. package/src/hooks/useIsAboveBreakpoint.ts +8 -0
  194. package/src/utils/date.ts +1 -0
  195. package/src/utils/fileDirectoryUtils.ts +12 -0
  196. package/src/utils/index.ts +2 -0
  197. package/src/utils/timeConstants.ts +19 -0
@@ -0,0 +1,610 @@
1
+ import { BottomSheetModal, BottomSheetModalProps } from '@gorhom/bottom-sheet';
2
+ import clsx from 'clsx';
3
+ import { styled } from 'nativewind';
4
+ import React, {
5
+ useState, useCallback, useRef, useEffect, forwardRef,
6
+ } from 'react';
7
+ import toast from 'react-hot-toast/headless';
8
+ import { ViewProps, ScrollView as RNScrollView } from 'react-native'; // eslint-disable-line no-restricted-imports
9
+ import { CameraIcon, PhotoIcon, FolderIcon } from 'react-native-heroicons/outline';
10
+ import { XMarkIcon } from 'react-native-heroicons/solid';
11
+ import ImageModal from 'react-native-image-modal';
12
+ import {
13
+ launchImageLibrary, PhotoQuality,
14
+ } from 'react-native-image-picker';
15
+
16
+ import BaseBottomSheetModal from './BaseBottomSheetModal';
17
+ import {
18
+ View, TouchableOpacity, ScrollView, Image,
19
+ } from '../hoc-components';
20
+ import BaseIcon from './BaseIcon';
21
+ import BaseSpinner from './BaseSpinner';
22
+ import BaseText from './BaseText';
23
+ import CameraWrapperModal, { CameraWrapperModalImage } from './CameraWrapperModal';
24
+ import { useIsAboveBreakpoint } from '../hooks/useIsAboveBreakpoint';
25
+
26
+ export interface CameraImage {
27
+ uri: string;
28
+ type?: string;
29
+ name?: string;
30
+ width?: number;
31
+ height?: number;
32
+ path?: string;
33
+ }
34
+
35
+ interface SingleImagePreviewProps {
36
+ image: CameraImage;
37
+ disabled: boolean;
38
+ handleRemoveImage: () => void;
39
+ }
40
+
41
+ function SingleImagePreview({ image, disabled, handleRemoveImage }: SingleImagePreviewProps) {
42
+ const largePhone = useIsAboveBreakpoint('xs');
43
+
44
+ return (
45
+ <View className="relative flex items-center justify-center self-center rounded-lg bg-secondary">
46
+ <ImageModal
47
+ source={{ uri: image.uri }}
48
+ resizeMode="cover"
49
+ imageBackgroundColor="black"
50
+ style={{ width: largePhone ? 200 : 120, height: largePhone ? 330 : 200 }}
51
+ />
52
+ {!disabled && (
53
+ <TouchableOpacity
54
+ onPress={() => handleRemoveImage()}
55
+ className="absolute -right-2 -top-2 rounded-full bg-error-primary p-1"
56
+ >
57
+ <BaseIcon
58
+ icon={XMarkIcon}
59
+ size={16}
60
+ color="fg-white"
61
+ />
62
+ </TouchableOpacity>
63
+ )}
64
+ </View>
65
+ );
66
+ }
67
+
68
+ interface MultipleImagePreviewProps {
69
+ images: CameraImage[];
70
+ disabled: boolean;
71
+ handleRemoveImage: (index: number) => void;
72
+ maxImages: number;
73
+ openCamera: () => void;
74
+ loading: boolean;
75
+ }
76
+
77
+ function MultipleImagePreview({
78
+ images, disabled, handleRemoveImage, maxImages, openCamera, loading,
79
+ }: MultipleImagePreviewProps) {
80
+ const largePhone = useIsAboveBreakpoint('xs');
81
+ const showAddButton = !disabled && images && images.length < maxImages;
82
+ const scrollViewRef = useRef<RNScrollView>(null);
83
+
84
+ const prevImagesLengthRef = useRef(0);
85
+
86
+ const itemWidth = largePhone ? 132 : 84;
87
+ const itemHeight = largePhone ? 176 : 112;
88
+
89
+ const getMarginRight = (isLastImage: boolean, imagesCount: number) => {
90
+ if (isLastImage) return 15;
91
+
92
+ if (imagesCount > 2) return -itemWidth * 0.30;
93
+
94
+ return 0;
95
+ };
96
+
97
+ useEffect(() => {
98
+ if (scrollViewRef.current && images.length > prevImagesLengthRef.current) {
99
+ setTimeout(() => {
100
+ scrollViewRef.current?.scrollToEnd({ animated: true });
101
+ }, 100);
102
+ }
103
+
104
+ prevImagesLengthRef.current = images.length;
105
+ }, [images.length]);
106
+
107
+ return (
108
+ <View>
109
+ <ScrollView
110
+ ref={scrollViewRef}
111
+ horizontal
112
+ showsHorizontalScrollIndicator={false}
113
+ contentContainerStyle={{ paddingHorizontal: 10, alignItems: 'center' }}
114
+ className="mb-3"
115
+ decelerationRate="fast"
116
+ >
117
+ <View className="flex flex-row">
118
+ {images.map((image, index) => {
119
+ const uniqueId = image.name
120
+ || image.uri.substring(image.uri.lastIndexOf('/') + 1)
121
+ || `image-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
122
+
123
+ const isLastImage = index === images.length - 1;
124
+
125
+ return (
126
+ <View
127
+ key={uniqueId}
128
+ className="overflow-hidden rounded-lg border border-brand-primary"
129
+ style={{
130
+ zIndex: images.length - index,
131
+ width: itemWidth,
132
+ height: itemHeight,
133
+ marginRight: getMarginRight(isLastImage, images.length),
134
+ }}
135
+ >
136
+ <ImageModal
137
+ resizeMode="cover"
138
+ imageBackgroundColor="black"
139
+ style={{ width: itemWidth, height: itemHeight }}
140
+ source={{ uri: image.uri }}
141
+ />
142
+
143
+ {!disabled && (
144
+ <TouchableOpacity
145
+ className="absolute right-2 top-1 rounded-full bg-error-solid p-1"
146
+ onPress={() => handleRemoveImage(index)}
147
+ >
148
+ <BaseIcon
149
+ icon={XMarkIcon}
150
+ size={16}
151
+ color="fg-white"
152
+ />
153
+ </TouchableOpacity>
154
+ )}
155
+ </View>
156
+ );
157
+ })}
158
+ </View>
159
+ </ScrollView>
160
+
161
+ {showAddButton && (
162
+ <TouchableOpacity
163
+ onPress={openCamera}
164
+ disabled={loading}
165
+ className={clsx(
166
+ 'flex w-full flex-col items-center justify-center rounded-lg border border-secondary py-1',
167
+ disabled && 'opacity-50',
168
+ )}
169
+ >
170
+ {loading ? (
171
+ <BaseSpinner size="small" />
172
+ ) : (
173
+ <>
174
+ <BaseIcon
175
+ icon={CameraIcon}
176
+ size={20}
177
+ color="fg-secondary"
178
+ />
179
+ <BaseText className="mt-1 text-center text-xs font-semibold text-brand-secondary">
180
+ Agregar foto
181
+ </BaseText>
182
+ </>
183
+ )}
184
+ </TouchableOpacity>
185
+ )}
186
+ </View>
187
+ );
188
+ }
189
+
190
+ interface InputMethodSelectionModalProps extends Omit<BottomSheetModalProps, 'children'> {
191
+ onClose: () => void;
192
+ onSelectCamera?: () => void;
193
+ onSelectGallery?: () => void;
194
+ onSelectCustomGallery?: () => void;
195
+ }
196
+
197
+ const InputMethodSelectionModal = forwardRef<BottomSheetModal, InputMethodSelectionModalProps>(({
198
+ onClose, onSelectCamera, onSelectGallery, onSelectCustomGallery, ...props
199
+ }, ref) => (
200
+ <BaseBottomSheetModal
201
+ ref={ref}
202
+ {...props}
203
+ >
204
+ <View className="p-4 pb-8">
205
+ <BaseText className="mb-4 text-center text-lg font-semibold text-secondary">
206
+ Seleccionar fuente
207
+ </BaseText>
208
+ <View className="space-y-3">
209
+ {onSelectCamera && (
210
+ <TouchableOpacity
211
+ onPress={onSelectCamera}
212
+ className="flex-row items-center rounded-lg border border-secondary p-4"
213
+ >
214
+ <BaseIcon
215
+ icon={CameraIcon}
216
+ size={24}
217
+ color="fg-secondary"
218
+ />
219
+ <BaseText className="ml-3 text-secondary">Tomar foto</BaseText>
220
+ </TouchableOpacity>
221
+ )}
222
+ {onSelectGallery && (
223
+ <TouchableOpacity
224
+ onPress={onSelectGallery}
225
+ className="flex-row items-center rounded-lg border border-secondary p-4"
226
+ >
227
+ <BaseIcon
228
+ icon={PhotoIcon}
229
+ size={24}
230
+ color="fg-secondary"
231
+ />
232
+ <BaseText className="ml-3 text-secondary">Seleccionar de galería</BaseText>
233
+ </TouchableOpacity>
234
+ )}
235
+ {onSelectCustomGallery && (
236
+ <TouchableOpacity
237
+ onPress={onSelectCustomGallery}
238
+ className="flex-row items-center rounded-lg border border-secondary p-4"
239
+ >
240
+ <BaseIcon
241
+ icon={FolderIcon}
242
+ size={24}
243
+ color="fg-secondary"
244
+ />
245
+ <BaseText className="ml-3 text-secondary">Archivos guardados</BaseText>
246
+ </TouchableOpacity>
247
+ )}
248
+ </View>
249
+ <TouchableOpacity
250
+ onPress={onClose}
251
+ className="mt-4 rounded-lg bg-gray-100 p-3"
252
+ >
253
+ <BaseText className="text-center text-secondary">Cancelar</BaseText>
254
+ </TouchableOpacity>
255
+ </View>
256
+ </BaseBottomSheetModal>
257
+ ));
258
+
259
+ interface CustomGalleryModalProps extends Omit<BottomSheetModalProps, 'children'> {
260
+ onClose: () => void;
261
+ customImages: CameraImage[];
262
+ onSelectFile: (file: CameraImage) => void;
263
+ }
264
+
265
+ const CustomGalleryModal = forwardRef<BottomSheetModal, CustomGalleryModalProps>(({
266
+ onClose, customImages, onSelectFile, ...props
267
+ }, ref) => (
268
+ <BaseBottomSheetModal
269
+ ref={ref}
270
+ {...props}
271
+ >
272
+ <View className="px-4 pb-4">
273
+ <View className="mb-4 flex-row items-center justify-between">
274
+ <BaseText className="text-lg font-semibold">Archivos guardados</BaseText>
275
+ <TouchableOpacity
276
+ onPress={onClose}
277
+ className="p-2"
278
+ >
279
+ <BaseIcon
280
+ icon={XMarkIcon}
281
+ size={24}
282
+ color="fg-secondary"
283
+ />
284
+ </TouchableOpacity>
285
+ </View>
286
+ {customImages.length > 0 && (
287
+ <ScrollView
288
+ showsVerticalScrollIndicator={false}
289
+ contentContainerStyle={{ paddingBottom: 20 }}
290
+ >
291
+ <View className="flex flex-row flex-wrap">
292
+ {customImages.map((image) => (
293
+ <View
294
+ key={image.uri}
295
+ className="mb-2 mr-2 aspect-[2/3] w-[30%]"
296
+ >
297
+ <TouchableOpacity
298
+ className="flex-1 overflow-hidden rounded-lg border border-primary"
299
+ onPress={() => onSelectFile(image)}
300
+ >
301
+ <Image
302
+ source={{ uri: image.uri }}
303
+ className="flex-1"
304
+ resizeMode="cover"
305
+ />
306
+ </TouchableOpacity>
307
+ </View>
308
+ ))}
309
+ </View>
310
+ </ScrollView>
311
+ )}
312
+ {customImages.length === 0 && (
313
+ <View className="flex-1 items-center justify-center">
314
+ <BaseText className="text-tertiary">No hay archivos guardados</BaseText>
315
+ </View>
316
+ )}
317
+ </View>
318
+ </BaseBottomSheetModal>
319
+ ));
320
+
321
+ export interface CameraImageInputProps extends ViewProps {
322
+ label?: string;
323
+ description?: string;
324
+ callToAction?: React.ReactNode;
325
+ disabled?: boolean;
326
+ multiple?: boolean;
327
+ maxImages?: number;
328
+ pathToSave?: string;
329
+ selectFromCamera?: boolean;
330
+ selectFromGallery?: boolean;
331
+ customImages?: CameraImage[];
332
+ imageOptions?: {
333
+ maxWidth?: number;
334
+ maxHeight?: number;
335
+ quality?: PhotoQuality;
336
+ includeBase64?: boolean;
337
+ };
338
+ value?: CameraImage | CameraImage[] | null;
339
+ onValueChange?: (value: CameraImage | CameraImage[] | null) => void;
340
+ showImages?: boolean;
341
+ hasError?: boolean;
342
+ }
343
+
344
+ function CameraImageInput({
345
+ label,
346
+ description,
347
+ callToAction,
348
+ disabled = false,
349
+ multiple = false,
350
+ maxImages = 5,
351
+ pathToSave,
352
+ selectFromCamera = false,
353
+ selectFromGallery = false,
354
+ customImages,
355
+ imageOptions = {
356
+ maxWidth: 1024,
357
+ maxHeight: 1024,
358
+ quality: 0.8,
359
+ includeBase64: false,
360
+ },
361
+ value,
362
+ onValueChange,
363
+ showImages = true,
364
+ hasError = false,
365
+ ...props
366
+ }: CameraImageInputProps) {
367
+ if (!selectFromCamera && !selectFromGallery && !customImages?.length) {
368
+ throw new Error('You must provide at least one of selectFromCamera, selectFromGallery, or customGalleryPath');
369
+ }
370
+
371
+ const [modalVisible, setModalVisible] = useState(false);
372
+ const [loading, setLoading] = useState(false);
373
+ const inputMethodBottomSheetRef = useRef<BottomSheetModal>(null);
374
+ const customGalleryBottomSheetRef = useRef<BottomSheetModal>(null);
375
+
376
+ const handleGallerySelect = useCallback(async () => {
377
+ try {
378
+ setLoading(true);
379
+ const result = await launchImageLibrary({
380
+ mediaType: 'photo',
381
+ selectionLimit: multiple ? maxImages : 1,
382
+ ...imageOptions,
383
+ });
384
+
385
+ if (result.didCancel || !result.assets?.length) {
386
+ return;
387
+ }
388
+
389
+ if (multiple) {
390
+ const newImages: CameraImage[] = result.assets.map((asset) => ({
391
+ uri: asset.uri!,
392
+ type: asset.type,
393
+ name: asset.fileName,
394
+ width: asset.width,
395
+ height: asset.height,
396
+ }));
397
+
398
+ const currentImages = (value as CameraImage[]) || [];
399
+ const combinedImages = [...currentImages, ...newImages];
400
+ onValueChange?.(combinedImages.slice(0, maxImages));
401
+ } else {
402
+ const asset = result.assets[0];
403
+ const newImage: CameraImage = {
404
+ uri: asset.uri!,
405
+ type: asset.type,
406
+ name: asset.fileName,
407
+ width: asset.width,
408
+ height: asset.height,
409
+ };
410
+ onValueChange?.(newImage);
411
+ }
412
+ } catch {
413
+ toast.error('Error al seleccionar la imagen');
414
+ } finally {
415
+ setLoading(false);
416
+ }
417
+ }, [value, onValueChange, multiple, maxImages, imageOptions]);
418
+
419
+ const handleCustomFileSelect = useCallback((file: CameraImage) => {
420
+ try {
421
+ const newImage = file;
422
+
423
+ if (multiple) {
424
+ const currentImages = (value as CameraImage[]) || [];
425
+ const newImages = [...currentImages, newImage];
426
+ onValueChange?.(newImages);
427
+ } else {
428
+ onValueChange?.(newImage);
429
+ }
430
+ customGalleryBottomSheetRef.current?.dismiss();
431
+ } catch {
432
+ toast.error('Error al procesar la imagen');
433
+ }
434
+ }, [value, onValueChange, multiple]);
435
+
436
+ const openInputMethod = useCallback(() => {
437
+ if (disabled || loading) return;
438
+
439
+ if (selectFromCamera && !(selectFromGallery || customImages?.length)) {
440
+ setModalVisible(true);
441
+ return;
442
+ }
443
+
444
+ if (!selectFromCamera && (selectFromGallery || customImages?.length)) {
445
+ if (customImages?.length) {
446
+ inputMethodBottomSheetRef.current?.present();
447
+ } else {
448
+ handleGallerySelect();
449
+ }
450
+ return;
451
+ }
452
+
453
+ inputMethodBottomSheetRef.current?.present();
454
+ }, [
455
+ disabled,
456
+ loading,
457
+ selectFromCamera,
458
+ selectFromGallery,
459
+ customImages,
460
+ handleGallerySelect,
461
+ ]);
462
+
463
+ const handleSelectCamera = useCallback(() => {
464
+ inputMethodBottomSheetRef.current?.dismiss();
465
+ setModalVisible(true);
466
+ }, []);
467
+
468
+ const handleSelectGallery = useCallback(() => {
469
+ inputMethodBottomSheetRef.current?.dismiss();
470
+ handleGallerySelect();
471
+ }, [handleGallerySelect]);
472
+
473
+ const handleSelectCustomGallery = useCallback(async () => {
474
+ inputMethodBottomSheetRef.current?.dismiss();
475
+ customGalleryBottomSheetRef.current?.present();
476
+ }, []);
477
+
478
+ const handleCapture = useCallback((photoFile: CameraWrapperModalImage) => {
479
+ try {
480
+ setLoading(true);
481
+ const newImage: CameraImage = {
482
+ uri: photoFile.uri,
483
+ path: photoFile.path,
484
+ type: 'image/jpeg',
485
+ name: `photo_${Date.now()}.jpg`,
486
+ width: photoFile.width,
487
+ height: photoFile.height,
488
+ };
489
+
490
+ if (multiple) {
491
+ const currentImages = (value as CameraImage[]) || [];
492
+ const newImages = [...currentImages, newImage];
493
+ onValueChange?.(newImages);
494
+ } else {
495
+ onValueChange?.(newImage);
496
+ }
497
+ setModalVisible(false);
498
+ } catch {
499
+ toast.error('Error al procesar la imagen');
500
+ } finally {
501
+ setLoading(false);
502
+ }
503
+ }, [value, onValueChange, multiple]);
504
+
505
+ const handleRemoveImage = useCallback((index?: number) => {
506
+ if (multiple && typeof index === 'number') {
507
+ const currentImages = (value as CameraImage[]) || [];
508
+ const newImages = [...currentImages];
509
+ newImages.splice(index, 1);
510
+ onValueChange?.(newImages.length > 0 ? newImages : null);
511
+ } else {
512
+ onValueChange?.(null);
513
+ }
514
+ }, [onValueChange, multiple, value]);
515
+
516
+ const hasImages = multiple ? (value as CameraImage[])?.length > 0 : !!value;
517
+
518
+ return (
519
+ <View
520
+ className="flex flex-col"
521
+ {...props}
522
+ >
523
+ <View>
524
+ {label && (
525
+ <BaseText className="font-semibold text-secondary">{label}</BaseText>
526
+ )}
527
+ {description && (
528
+ <BaseText className="text-tertiary">{description}</BaseText>
529
+ )}
530
+ </View>
531
+ <View className={clsx(
532
+ 'rounded-lg border',
533
+ !(value && (value as CameraImage[]).length > 0) && 'h-full',
534
+ showImages && 'p-2',
535
+ disabled ? 'border-disabled bg-disabled' : 'border-primary bg-primary',
536
+ hasError && 'border-error',
537
+ )}
538
+ >
539
+ {(!showImages || !hasImages || (!hasImages && multiple)) && (
540
+ <View className="flex flex-1 flex-row justify-center space-x-4">
541
+ <TouchableOpacity
542
+ onPress={openInputMethod}
543
+ disabled={disabled || loading}
544
+ className={clsx(
545
+ 'flex flex-1 flex-col items-center justify-center rounded-lg',
546
+ disabled ? 'opacity-50' : '',
547
+ showImages && hasImages && 'border border-secondary',
548
+ )}
549
+ >
550
+ {loading ? (
551
+ <BaseSpinner size="small" />
552
+ ) : (
553
+ <>
554
+ <BaseIcon
555
+ icon={(() => {
556
+ if (selectFromCamera && selectFromGallery) return PhotoIcon;
557
+ if (selectFromCamera) return CameraIcon;
558
+ return PhotoIcon;
559
+ })()}
560
+ size={24}
561
+ color="fg-secondary"
562
+ />
563
+ {callToAction}
564
+ </>
565
+ )}
566
+ </TouchableOpacity>
567
+ </View>
568
+ )}
569
+ {showImages && !multiple && value && (
570
+ <SingleImagePreview
571
+ image={value as CameraImage}
572
+ disabled={disabled}
573
+ handleRemoveImage={() => handleRemoveImage()}
574
+ />
575
+ )}
576
+ {showImages && multiple && value && (value as CameraImage[]).length > 0 && (
577
+ <MultipleImagePreview
578
+ images={value as CameraImage[]}
579
+ disabled={disabled}
580
+ handleRemoveImage={handleRemoveImage}
581
+ maxImages={maxImages}
582
+ openCamera={openInputMethod}
583
+ loading={loading}
584
+ />
585
+ )}
586
+ </View>
587
+ <InputMethodSelectionModal
588
+ ref={inputMethodBottomSheetRef}
589
+ onClose={() => inputMethodBottomSheetRef.current?.dismiss()}
590
+ onSelectCamera={selectFromCamera ? handleSelectCamera : undefined}
591
+ onSelectGallery={selectFromGallery ? handleSelectGallery : undefined}
592
+ onSelectCustomGallery={customImages?.length ? handleSelectCustomGallery : undefined}
593
+ />
594
+ <CustomGalleryModal
595
+ ref={customGalleryBottomSheetRef}
596
+ onClose={() => customGalleryBottomSheetRef.current?.dismiss()}
597
+ customImages={customImages || []}
598
+ onSelectFile={handleCustomFileSelect}
599
+ />
600
+ <CameraWrapperModal
601
+ pathToSave={pathToSave}
602
+ visible={modalVisible}
603
+ onClose={() => setModalVisible(false)}
604
+ onCapture={handleCapture}
605
+ />
606
+ </View>
607
+ );
608
+ }
609
+
610
+ export default styled(CameraImageInput);