@herca/rn-kit 0.1.101 → 0.1.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/Icon/Icon.js +3 -1
- package/lib/module/Icon/Icon.js.map +1 -1
- package/lib/module/Icon/icons/ScanQr.js +22 -0
- package/lib/module/Icon/icons/ScanQr.js.map +1 -0
- package/lib/module/Input/InputFile.js +178 -68
- package/lib/module/Input/InputFile.js.map +1 -1
- package/lib/module/Input/README_INPUT_FILE.md +179 -105
- package/lib/module/Input/TextArea.js +2 -0
- package/lib/module/Input/TextArea.js.map +1 -1
- package/lib/module/Input/helpers/uploadFile.js +60 -0
- package/lib/module/Input/helpers/uploadFile.js.map +1 -0
- package/lib/module/Input/partials/InputFile/CardTriggerSmall.js +1 -0
- package/lib/module/Input/partials/InputFile/CardTriggerSmall.js.map +1 -1
- package/lib/module/Input/partials/InputFile/ItemPreview.js +15 -4
- package/lib/module/Input/partials/InputFile/ItemPreview.js.map +1 -1
- package/lib/typescript/src/Icon/Icon.d.ts.map +1 -1
- package/lib/typescript/src/Icon/icons/ScanQr.d.ts +9 -0
- package/lib/typescript/src/Icon/icons/ScanQr.d.ts.map +1 -0
- package/lib/typescript/src/Icon/type.d.ts +1 -1
- package/lib/typescript/src/Icon/type.d.ts.map +1 -1
- package/lib/typescript/src/Input/InputFile.d.ts +3 -48
- package/lib/typescript/src/Input/InputFile.d.ts.map +1 -1
- package/lib/typescript/src/Input/TextArea.d.ts +1 -1
- package/lib/typescript/src/Input/TextArea.d.ts.map +1 -1
- package/lib/typescript/src/Input/helpers/uploadFile.d.ts +3 -0
- package/lib/typescript/src/Input/helpers/uploadFile.d.ts.map +1 -0
- package/lib/typescript/src/Input/partials/InputFile/CardTriggerSmall.d.ts.map +1 -1
- package/lib/typescript/src/Input/partials/InputFile/ItemPreview.d.ts +4 -2
- package/lib/typescript/src/Input/partials/InputFile/ItemPreview.d.ts.map +1 -1
- package/lib/typescript/src/Input/type.d.ts +91 -0
- package/lib/typescript/src/Input/type.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Icon/Icon.tsx +2 -0
- package/src/Icon/icons/ScanQr.tsx +19 -0
- package/src/Icon/type.tsx +1 -0
- package/src/Input/InputFile.tsx +235 -111
- package/src/Input/README_INPUT_FILE.md +179 -105
- package/src/Input/TextArea.tsx +2 -1
- package/src/Input/helpers/uploadFile.ts +77 -0
- package/src/Input/partials/InputFile/CardTriggerSmall.tsx +1 -0
- package/src/Input/partials/InputFile/ItemPreview.tsx +25 -4
- package/src/Input/type.tsx +105 -0
package/src/Input/InputFile.tsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useState, useRef } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import Color from '../Color/Color';
|
|
4
4
|
import Typography from '../Typography/Typography';
|
|
5
5
|
|
|
6
|
-
import { pick } from '@react-native-documents/picker';
|
|
6
|
+
import { pick, types } from '@react-native-documents/picker';
|
|
7
7
|
import { viewDocument } from '@react-native-documents/viewer';
|
|
8
8
|
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
|
|
9
9
|
import { spacing } from '../styles/spacing';
|
|
@@ -14,64 +14,24 @@ import ModalDelete from './partials/InputFile/ModalDelete';
|
|
|
14
14
|
import ModalPicker from './partials/InputFile/ModalPicker';
|
|
15
15
|
import CardTriggerSmall from './partials/InputFile/CardTriggerSmall';
|
|
16
16
|
import ReactNativeBlobUtil from 'react-native-blob-util';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
description?: string;
|
|
25
|
-
camera?: ModalOption;
|
|
26
|
-
gallery?: ModalOption;
|
|
27
|
-
document?: ModalOption;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ChangeLabelProps {
|
|
31
|
-
label?: string;
|
|
32
|
-
placeholder?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type BaseInputFileProps = {
|
|
36
|
-
title?: string;
|
|
37
|
-
accept?: string[];
|
|
38
|
-
multiple?: boolean;
|
|
39
|
-
onChange?: (files: any) => void;
|
|
40
|
-
value?: any[];
|
|
41
|
-
modalPickFileText?: ModalPickFileText;
|
|
42
|
-
btnChooseFileText?: string;
|
|
43
|
-
modalDeleteText?: ModalOption & {
|
|
44
|
-
confirmBtn?: {
|
|
45
|
-
confirm?: string;
|
|
46
|
-
cancel?: string;
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
hasError?: boolean;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
type DefaultVariantProps = BaseInputFileProps & {
|
|
53
|
-
variant?: 'default';
|
|
54
|
-
description?: string;
|
|
55
|
-
useChangeLabel?: boolean;
|
|
56
|
-
changeLableProps?: ChangeLabelProps;
|
|
57
|
-
hint?: string;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
type SmallVariantProps = BaseInputFileProps & {
|
|
61
|
-
variant: 'small';
|
|
62
|
-
description?: never;
|
|
63
|
-
useChangeLabel?: never;
|
|
64
|
-
changeLableProps?: never;
|
|
65
|
-
hint?: never;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Union type
|
|
69
|
-
type InputFileProps = DefaultVariantProps | SmallVariantProps;
|
|
17
|
+
import type {
|
|
18
|
+
FileItem,
|
|
19
|
+
InputFileProps,
|
|
20
|
+
UploadConfig,
|
|
21
|
+
UploadedFile,
|
|
22
|
+
} from './type';
|
|
23
|
+
import { uploadFile } from './helpers/uploadFile';
|
|
70
24
|
|
|
71
25
|
export default function InputFile({
|
|
72
26
|
title = 'Upload File',
|
|
73
27
|
description = 'File harus berformat JPG, PNG dan PDF',
|
|
74
|
-
accept = [
|
|
28
|
+
accept = [
|
|
29
|
+
'application/pdf',
|
|
30
|
+
'image/jpeg',
|
|
31
|
+
'image/png',
|
|
32
|
+
types.pdf,
|
|
33
|
+
types.images,
|
|
34
|
+
],
|
|
75
35
|
btnChooseFileText = 'Choose File',
|
|
76
36
|
multiple = false,
|
|
77
37
|
value,
|
|
@@ -83,39 +43,81 @@ export default function InputFile({
|
|
|
83
43
|
hint,
|
|
84
44
|
hasError,
|
|
85
45
|
onChange,
|
|
46
|
+
uploadConfig,
|
|
47
|
+
maxSize = 5, //5MB
|
|
48
|
+
maxSizeErrorMessage,
|
|
86
49
|
}: InputFileProps) {
|
|
87
|
-
const [isOpenBottomSheetTypeFile, setIsOpenBottomSheetTypeFile] =
|
|
88
|
-
|
|
89
|
-
const [
|
|
90
|
-
|
|
91
|
-
const [
|
|
92
|
-
const [
|
|
93
|
-
|
|
94
|
-
);
|
|
50
|
+
const [isOpenBottomSheetTypeFile, setIsOpenBottomSheetTypeFile] = useState(false); //prettier-ignore
|
|
51
|
+
const [isOpenBottomSheetDeleteFile, setIsOpenBottomSheetDeleteFile] = useState(false); //prettier-ignore
|
|
52
|
+
const [selectedFileIndex, setSelectedFileIndex] = useState<number | null>(null); //prettier-ignore
|
|
53
|
+
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile<unknown>[]>([]); //prettier-ignore
|
|
54
|
+
const [internalErrorMessage, setInternalErrorMessage] = useState<string | null>(null); //prettier-ignore
|
|
55
|
+
const [files, setFiles] = useState<FileItem[]>(value || []);
|
|
56
|
+
const filesRef = useRef<FileItem[]>(files);
|
|
95
57
|
|
|
96
|
-
const
|
|
97
|
-
setIsOpenBottomSheetTypeFile(true);
|
|
98
|
-
};
|
|
58
|
+
const calculatedMaxSize = maxSize * 1024 * 1024;
|
|
99
59
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
if (selectedFileIndex !== null) {
|
|
103
|
-
confirmReplaceFile(selectedFileIndex);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
60
|
+
const uploadAndTrack = async (newFiles: FileItem[], config: UploadConfig) => {
|
|
61
|
+
setIsOpenBottomSheetTypeFile(false);
|
|
106
62
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
63
|
+
const withLoading = newFiles.map((f) => ({ ...f, uploading: true }));
|
|
64
|
+
setFiles((prev) => [...prev, ...withLoading]);
|
|
65
|
+
|
|
66
|
+
const results = await Promise.all(
|
|
67
|
+
newFiles.map((f) => uploadFile(f, config))
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const currentFiles = filesRef.current;
|
|
71
|
+
const updatedFiles = currentFiles.map((f) => {
|
|
72
|
+
const match = results.find(
|
|
73
|
+
(r) => r.originalName === ('name' in f ? f.name : f.fileName)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!match) return f;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...f,
|
|
80
|
+
uploading: false,
|
|
81
|
+
error: match.uploadedData === null,
|
|
82
|
+
uploadedData: match.uploadedData,
|
|
83
|
+
hint:
|
|
84
|
+
match.uploadedData === null
|
|
85
|
+
? (config.errorMessage ?? 'Upload gagal')
|
|
86
|
+
: f.hint,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
setFiles(updatedFiles);
|
|
91
|
+
|
|
92
|
+
const newUploaded = [...uploadedFiles, ...results];
|
|
93
|
+
setUploadedFiles(newUploaded);
|
|
94
|
+
uploadConfig?.onUploadSuccess?.(newUploaded);
|
|
95
|
+
onChange?.(updatedFiles);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// validate max size
|
|
99
|
+
const validateMaxSize = (
|
|
100
|
+
assets: { size?: number | null; fileSize?: number | null }[]
|
|
101
|
+
): boolean => {
|
|
102
|
+
if (!maxSize) return true;
|
|
103
|
+
|
|
104
|
+
const oversized = assets.filter((f) => {
|
|
105
|
+
const size = f.size ?? f.fileSize ?? 0;
|
|
106
|
+
return size > (calculatedMaxSize ?? Infinity);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (oversized.length > 0) {
|
|
110
|
+
setInternalErrorMessage(
|
|
111
|
+
maxSizeErrorMessage ?? 'Ukuran file melebihi batas maksimal'
|
|
112
|
+
);
|
|
112
113
|
setIsOpenBottomSheetTypeFile(false);
|
|
113
|
-
|
|
114
|
-
if (error?.message?.toLowerCase().includes('user canceled')) return;
|
|
115
|
-
console.error('Failed to pick document:', error);
|
|
114
|
+
return false;
|
|
116
115
|
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
117
118
|
};
|
|
118
119
|
|
|
120
|
+
// preview file
|
|
119
121
|
const handlePreviewFile = async (file: any) => {
|
|
120
122
|
try {
|
|
121
123
|
let fileUri = file.uri;
|
|
@@ -145,18 +147,63 @@ export default function InputFile({
|
|
|
145
147
|
}
|
|
146
148
|
};
|
|
147
149
|
|
|
150
|
+
// pick file
|
|
151
|
+
const handlePickFile = async () => {
|
|
152
|
+
try {
|
|
153
|
+
if (selectedFileIndex !== null) {
|
|
154
|
+
confirmReplaceFile(selectedFileIndex);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = await pick({
|
|
159
|
+
type: accept,
|
|
160
|
+
allowMultiSelection: multiple,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const pickedFiles = multiple ? result : [result[0]];
|
|
164
|
+
|
|
165
|
+
if (!validateMaxSize(pickedFiles)) return;
|
|
166
|
+
setInternalErrorMessage(null);
|
|
167
|
+
|
|
168
|
+
if (uploadConfig) {
|
|
169
|
+
await uploadAndTrack(
|
|
170
|
+
pickedFiles as unknown as FileItem[],
|
|
171
|
+
uploadConfig
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
const newFiles = [...files, ...(pickedFiles as unknown as FileItem[])];
|
|
175
|
+
setFiles(newFiles);
|
|
176
|
+
onChange?.(newFiles);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
setIsOpenBottomSheetTypeFile(false);
|
|
180
|
+
} catch (error: any) {
|
|
181
|
+
if (error?.message?.toLowerCase().includes('user canceled')) return;
|
|
182
|
+
console.error('Failed to pick document:', error);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
148
186
|
const handleOnPressButtonCamera = async () => {
|
|
149
187
|
try {
|
|
150
188
|
if (selectedFileIndex !== null) {
|
|
151
|
-
confirmReplaceFile(selectedFileIndex, 'camera');
|
|
189
|
+
await confirmReplaceFile(selectedFileIndex, 'camera');
|
|
152
190
|
return;
|
|
153
191
|
}
|
|
154
192
|
|
|
155
193
|
const result = await launchCamera({ mediaType: 'photo' });
|
|
156
194
|
if (result.didCancel || !result.assets) return;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
195
|
+
|
|
196
|
+
if (!validateMaxSize(result.assets)) return;
|
|
197
|
+
setInternalErrorMessage(null);
|
|
198
|
+
|
|
199
|
+
if (uploadConfig) {
|
|
200
|
+
await uploadAndTrack(result.assets as FileItem[], uploadConfig);
|
|
201
|
+
} else {
|
|
202
|
+
const newFiles = [...files, result.assets[0] as FileItem];
|
|
203
|
+
setFiles(newFiles);
|
|
204
|
+
onChange?.(newFiles);
|
|
205
|
+
}
|
|
206
|
+
|
|
160
207
|
setIsOpenBottomSheetTypeFile(false);
|
|
161
208
|
} catch (error: any) {
|
|
162
209
|
console.error('Failed to open camera:', error);
|
|
@@ -166,7 +213,7 @@ export default function InputFile({
|
|
|
166
213
|
const handleOnPressButtonGallery = async () => {
|
|
167
214
|
try {
|
|
168
215
|
if (selectedFileIndex !== null) {
|
|
169
|
-
confirmReplaceFile(selectedFileIndex, 'gallery');
|
|
216
|
+
await confirmReplaceFile(selectedFileIndex, 'gallery');
|
|
170
217
|
return;
|
|
171
218
|
}
|
|
172
219
|
|
|
@@ -175,9 +222,18 @@ export default function InputFile({
|
|
|
175
222
|
selectionLimit: multiple ? 0 : 1,
|
|
176
223
|
});
|
|
177
224
|
if (result.didCancel || !result.assets) return;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
225
|
+
|
|
226
|
+
if (!validateMaxSize(result.assets)) return;
|
|
227
|
+
setInternalErrorMessage(null);
|
|
228
|
+
|
|
229
|
+
if (uploadConfig) {
|
|
230
|
+
await uploadAndTrack(result.assets as FileItem[], uploadConfig);
|
|
231
|
+
} else {
|
|
232
|
+
const newFiles = [...files, ...(result.assets as FileItem[])];
|
|
233
|
+
setFiles(newFiles);
|
|
234
|
+
onChange?.(newFiles);
|
|
235
|
+
}
|
|
236
|
+
|
|
181
237
|
setIsOpenBottomSheetTypeFile(false);
|
|
182
238
|
} catch (error: any) {
|
|
183
239
|
console.error('Failed to open gallery:', error);
|
|
@@ -192,13 +248,16 @@ export default function InputFile({
|
|
|
192
248
|
const deleteFile = () => {
|
|
193
249
|
if (selectedFileIndex !== null) {
|
|
194
250
|
const newFiles = files.filter((_, i) => i !== selectedFileIndex);
|
|
251
|
+
|
|
195
252
|
setFiles(newFiles);
|
|
253
|
+
setUploadedFiles(newFiles as unknown as UploadedFile<unknown>[]);
|
|
196
254
|
onChange?.(newFiles);
|
|
197
255
|
setSelectedFileIndex(null);
|
|
198
256
|
}
|
|
199
257
|
setIsOpenBottomSheetDeleteFile(false);
|
|
200
258
|
};
|
|
201
259
|
|
|
260
|
+
// replace attachment
|
|
202
261
|
const handleReplaceFile = (index: number) => {
|
|
203
262
|
setIsOpenBottomSheetTypeFile(true);
|
|
204
263
|
setSelectedFileIndex(index);
|
|
@@ -208,33 +267,91 @@ export default function InputFile({
|
|
|
208
267
|
index: number,
|
|
209
268
|
type?: 'gallery' | 'camera'
|
|
210
269
|
) => {
|
|
211
|
-
|
|
270
|
+
const labelFile = files?.[index]?.labelFile;
|
|
271
|
+
|
|
272
|
+
// handler untuk pick
|
|
273
|
+
const pickFile = async (): Promise<FileItem | null> => {
|
|
274
|
+
if (type === 'gallery') {
|
|
275
|
+
const result = await launchImageLibrary({
|
|
276
|
+
mediaType: 'photo',
|
|
277
|
+
selectionLimit: multiple ? 0 : 1,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (!result.assets?.length) return null;
|
|
281
|
+
return { ...result.assets[0], labelFile };
|
|
282
|
+
}
|
|
212
283
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
284
|
+
if (type === 'camera') {
|
|
285
|
+
const result = await launchCamera({ mediaType: 'photo' });
|
|
286
|
+
|
|
287
|
+
if (!result.assets?.length) return null;
|
|
288
|
+
return { ...result.assets[0], labelFile: files?.[index]?.labelFile };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const result = await pick({
|
|
292
|
+
type: accept,
|
|
293
|
+
allowMultiSelection: multiple,
|
|
217
294
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
295
|
+
|
|
296
|
+
if (!result?.length) return null;
|
|
297
|
+
return { ...result[0], labelFile } as unknown as FileItem;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const replacedFile = await pickFile();
|
|
301
|
+
if (!replacedFile) return;
|
|
302
|
+
|
|
303
|
+
// handle validasi max size
|
|
304
|
+
if (!validateMaxSize([replacedFile])) return;
|
|
305
|
+
setInternalErrorMessage(null);
|
|
306
|
+
|
|
307
|
+
// handle upload
|
|
308
|
+
if (uploadConfig) {
|
|
309
|
+
setIsOpenBottomSheetTypeFile(false);
|
|
310
|
+
|
|
311
|
+
const withLoading = files.map((f, i) =>
|
|
312
|
+
i === index ? { ...replacedFile, uploading: true } : f
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
setFiles(withLoading);
|
|
316
|
+
|
|
317
|
+
const results = await Promise.all([
|
|
318
|
+
uploadFile(replacedFile, uploadConfig),
|
|
319
|
+
]);
|
|
320
|
+
|
|
321
|
+
const uploadResult = results[0];
|
|
322
|
+
const updatedFiles = filesRef.current.map((f, i) =>
|
|
323
|
+
i === index
|
|
324
|
+
? {
|
|
325
|
+
...replacedFile,
|
|
326
|
+
uploading: false,
|
|
327
|
+
uploadedData: uploadResult.uploadedData,
|
|
328
|
+
error: uploadResult.uploadedData === null,
|
|
329
|
+
hint:
|
|
330
|
+
uploadResult.uploadedData === null
|
|
331
|
+
? (uploadConfig.errorMessage ?? 'Upload gagal')
|
|
332
|
+
: replacedFile.hint,
|
|
333
|
+
}
|
|
334
|
+
: f
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
setFiles(updatedFiles);
|
|
338
|
+
onChange?.(updatedFiles);
|
|
224
339
|
} else {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
340
|
+
const updatedFiles = files.map((f, i) =>
|
|
341
|
+
i === index ? replacedFile : f
|
|
342
|
+
);
|
|
343
|
+
setFiles(updatedFiles);
|
|
344
|
+
onChange?.(updatedFiles);
|
|
228
345
|
}
|
|
229
346
|
|
|
230
|
-
const updatedFiles = files.map((f, i) => (i === index ? replacedFile : f));
|
|
231
|
-
|
|
232
|
-
setFiles(updatedFiles);
|
|
233
|
-
onChange?.(updatedFiles);
|
|
234
347
|
setSelectedFileIndex(null);
|
|
235
348
|
setIsOpenBottomSheetTypeFile(false);
|
|
236
349
|
};
|
|
237
350
|
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
filesRef.current = files;
|
|
353
|
+
}, [files]);
|
|
354
|
+
|
|
238
355
|
useEffect(() => {
|
|
239
356
|
setFiles(value ?? []);
|
|
240
357
|
}, [value]);
|
|
@@ -244,11 +361,11 @@ export default function InputFile({
|
|
|
244
361
|
{variant === 'default' && (
|
|
245
362
|
<CardTrigger
|
|
246
363
|
title={title}
|
|
247
|
-
hint={hint}
|
|
248
|
-
hasError={hasError}
|
|
364
|
+
hint={hint || (internalErrorMessage as string)}
|
|
365
|
+
hasError={hasError || !!internalErrorMessage}
|
|
249
366
|
description={description}
|
|
250
367
|
btnSelect={btnChooseFileText}
|
|
251
|
-
onPress={
|
|
368
|
+
onPress={() => setIsOpenBottomSheetTypeFile(true)}
|
|
252
369
|
/>
|
|
253
370
|
)}
|
|
254
371
|
|
|
@@ -259,11 +376,17 @@ export default function InputFile({
|
|
|
259
376
|
title={title}
|
|
260
377
|
hasError={hasError}
|
|
261
378
|
textButton={btnChooseFileText}
|
|
262
|
-
onChooseFile={
|
|
379
|
+
onChooseFile={() => setIsOpenBottomSheetTypeFile(true)}
|
|
263
380
|
onPreview={handlePreviewFile}
|
|
264
381
|
onReplace={handleReplaceFile}
|
|
265
382
|
onDelete={confirmDeleteFile}
|
|
266
383
|
/>
|
|
384
|
+
{internalErrorMessage && (
|
|
385
|
+
<Typography variant="t3" color={Color.danger[500]}>
|
|
386
|
+
{internalErrorMessage}
|
|
387
|
+
</Typography>
|
|
388
|
+
)}
|
|
389
|
+
|
|
267
390
|
{files.length > 0 && (
|
|
268
391
|
<>
|
|
269
392
|
{files
|
|
@@ -306,6 +429,7 @@ export default function InputFile({
|
|
|
306
429
|
<ItemPreview
|
|
307
430
|
index={index}
|
|
308
431
|
file={file}
|
|
432
|
+
loading={file.uploading}
|
|
309
433
|
onPress={() => handlePreviewFile(file)}
|
|
310
434
|
onReplace={() => handleReplaceFile(index)}
|
|
311
435
|
onDelete={() => confirmDeleteFile(index)}
|