@herca/rn-kit 0.1.41 → 0.1.43
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 +7 -1
- package/lib/module/Icon/Icon.js.map +1 -1
- package/lib/module/Icon/README.md +3 -0
- package/lib/module/Icon/icons/ExclamationTriangle.js +22 -0
- package/lib/module/Icon/icons/ExclamationTriangle.js.map +1 -0
- package/lib/module/Icon/icons/PdfFile.js +36 -0
- package/lib/module/Icon/icons/PdfFile.js.map +1 -0
- package/lib/module/Icon/icons/RotateRight.js +22 -0
- package/lib/module/Icon/icons/RotateRight.js.map +1 -0
- package/lib/module/Input/InputFile.js +194 -59
- package/lib/module/Input/InputFile.js.map +1 -1
- package/lib/module/Input/README_INPUT_FILE.md +132 -0
- package/lib/module/Ui/Container.js.map +1 -1
- package/lib/typescript/src/Icon/Icon.d.ts.map +1 -1
- package/lib/typescript/src/Icon/icons/ExclamationTriangle.d.ts +9 -0
- package/lib/typescript/src/Icon/icons/ExclamationTriangle.d.ts.map +1 -0
- package/lib/typescript/src/Icon/icons/PdfFile.d.ts +11 -0
- package/lib/typescript/src/Icon/icons/PdfFile.d.ts.map +1 -0
- package/lib/typescript/src/Icon/icons/RotateRight.d.ts +9 -0
- package/lib/typescript/src/Icon/icons/RotateRight.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 +23 -3
- package/lib/typescript/src/Input/InputFile.d.ts.map +1 -1
- package/lib/typescript/src/Ui/Container.d.ts +7 -1
- package/lib/typescript/src/Ui/Container.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Icon/Icon.tsx +6 -0
- package/src/Icon/README.md +3 -0
- package/src/Icon/icons/ExclamationTriangle.tsx +23 -0
- package/src/Icon/icons/PdfFile.tsx +40 -0
- package/src/Icon/icons/RotateRight.tsx +23 -0
- package/src/Icon/type.tsx +3 -0
- package/src/Input/InputFile.tsx +235 -64
- package/src/Input/README_INPUT_FILE.md +132 -0
- package/src/Ui/Container.tsx +7 -2
package/src/Input/InputFile.tsx
CHANGED
|
@@ -1,41 +1,67 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
1
2
|
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import BottomSheet from '../BottomSheet/BottomSheet';
|
|
2
4
|
import Button from '../Button/Button';
|
|
3
5
|
import Color from '../Color/Color';
|
|
4
|
-
import Card from '../Ui/Card';
|
|
5
|
-
import Center from '../Ui/Center';
|
|
6
|
-
import Typography from '../Typography/Typography';
|
|
7
|
-
import BottomSheet from '../BottomSheet/BottomSheet';
|
|
8
|
-
import { useState } from 'react';
|
|
9
6
|
import List from '../List/List';
|
|
10
7
|
import ListItem from '../List/ListItem';
|
|
8
|
+
import Typography from '../Typography/Typography';
|
|
9
|
+
import Card from '../Ui/Card';
|
|
10
|
+
import Center from '../Ui/Center';
|
|
11
11
|
|
|
12
12
|
import { pick } from '@react-native-documents/picker';
|
|
13
13
|
import { viewDocument } from '@react-native-documents/viewer';
|
|
14
14
|
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
|
|
15
15
|
import Icon from '../Icon';
|
|
16
|
+
import Input from './Input';
|
|
17
|
+
|
|
18
|
+
export interface ModalOption {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface modalPickFileText {
|
|
23
|
+
title?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
camera?: ModalOption;
|
|
26
|
+
gallery?: ModalOption;
|
|
27
|
+
document?: ModalOption;
|
|
28
|
+
}
|
|
16
29
|
|
|
17
30
|
type InputFileProps = {
|
|
18
31
|
title?: string;
|
|
19
32
|
description?: string;
|
|
20
|
-
maxFileSize?: number;
|
|
21
33
|
accept?: string[];
|
|
22
34
|
multiple?: boolean;
|
|
23
|
-
onChange?: (files: any
|
|
35
|
+
onChange?: (files: any) => void;
|
|
36
|
+
value?: any[];
|
|
37
|
+
modalPickFileText?: modalPickFileText;
|
|
38
|
+
useChangeLabel?: boolean;
|
|
39
|
+
btnChooseFileText?: string;
|
|
40
|
+
modalDeleteText?: ModalOption & {
|
|
41
|
+
confirmBtn?: {
|
|
42
|
+
confirm?: string;
|
|
43
|
+
cancel?: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
24
46
|
};
|
|
25
47
|
|
|
26
48
|
export default function InputFile({
|
|
27
49
|
title = 'Upload File',
|
|
28
50
|
description = 'File harus berformat JPG, PNG dan PDF',
|
|
29
|
-
maxFileSize = 5,
|
|
30
51
|
accept = ['application/pdf', 'image/jpeg', 'image/png'],
|
|
52
|
+
btnChooseFileText = 'Choose File',
|
|
31
53
|
multiple = false,
|
|
54
|
+
value,
|
|
55
|
+
modalPickFileText,
|
|
56
|
+
modalDeleteText,
|
|
57
|
+
useChangeLabel = false,
|
|
32
58
|
onChange,
|
|
33
59
|
}: InputFileProps) {
|
|
34
60
|
const [isOpenBottomSheetTypeFile, setIsOpenBottomSheetTypeFile] =
|
|
35
61
|
useState(false);
|
|
36
62
|
const [isOpenBottomSheetDeleteFile, setIsOpenBottomSheetDeleteFile] =
|
|
37
63
|
useState(false);
|
|
38
|
-
const [files, setFiles] = useState<any[]>([]);
|
|
64
|
+
const [files, setFiles] = useState<any[]>(value || []);
|
|
39
65
|
const [selectedFileIndex, setSelectedFileIndex] = useState<number | null>(
|
|
40
66
|
null
|
|
41
67
|
);
|
|
@@ -46,6 +72,12 @@ export default function InputFile({
|
|
|
46
72
|
|
|
47
73
|
const handlePickFile = async () => {
|
|
48
74
|
try {
|
|
75
|
+
if (selectedFileIndex !== null) {
|
|
76
|
+
console.log(selectedFileIndex);
|
|
77
|
+
confirmReplaceFile(selectedFileIndex);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
49
81
|
const result = await pick({ type: accept, multiple });
|
|
50
82
|
const pickedFiles = multiple ? result : [result[0]];
|
|
51
83
|
const newFiles = [...files, ...pickedFiles];
|
|
@@ -60,12 +92,19 @@ export default function InputFile({
|
|
|
60
92
|
|
|
61
93
|
const handlePreviewFile = (file: any) => {
|
|
62
94
|
if (file?.uri) {
|
|
63
|
-
viewDocument(file.uri)
|
|
95
|
+
viewDocument({ uri: file.uri, mimeType: file.type }).catch((error) =>
|
|
96
|
+
console.error('Error opening document:', error)
|
|
97
|
+
);
|
|
64
98
|
}
|
|
65
99
|
};
|
|
66
100
|
|
|
67
101
|
const handleOnPressButtonCamera = async () => {
|
|
68
102
|
try {
|
|
103
|
+
if (selectedFileIndex !== null) {
|
|
104
|
+
confirmReplaceFile(selectedFileIndex, 'camera');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
69
108
|
const result = await launchCamera({ mediaType: 'photo' });
|
|
70
109
|
if (result.didCancel || !result.assets) return;
|
|
71
110
|
const newFiles = [...files, result.assets[0]];
|
|
@@ -79,6 +118,11 @@ export default function InputFile({
|
|
|
79
118
|
|
|
80
119
|
const handleOnPressButtonGallery = async () => {
|
|
81
120
|
try {
|
|
121
|
+
if (selectedFileIndex !== null) {
|
|
122
|
+
confirmReplaceFile(selectedFileIndex, 'gallery');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
82
126
|
const result = await launchImageLibrary({
|
|
83
127
|
mediaType: 'photo',
|
|
84
128
|
selectionLimit: multiple ? 0 : 1,
|
|
@@ -108,6 +152,46 @@ export default function InputFile({
|
|
|
108
152
|
setIsOpenBottomSheetDeleteFile(false);
|
|
109
153
|
};
|
|
110
154
|
|
|
155
|
+
const handleReplaceFile = (index: number) => {
|
|
156
|
+
setIsOpenBottomSheetTypeFile(true);
|
|
157
|
+
setSelectedFileIndex(index);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const confirmReplaceFile = async (
|
|
161
|
+
index: number,
|
|
162
|
+
type?: 'gallery' | 'camera'
|
|
163
|
+
) => {
|
|
164
|
+
let replacedFile: any | null = null;
|
|
165
|
+
|
|
166
|
+
if (type === 'gallery') {
|
|
167
|
+
const result = await launchImageLibrary({
|
|
168
|
+
mediaType: 'photo',
|
|
169
|
+
selectionLimit: multiple ? 0 : 1,
|
|
170
|
+
});
|
|
171
|
+
if (!result.assets || result.assets.length === 0) return;
|
|
172
|
+
replacedFile = { ...result.assets[0], labelFile: files[index].labelFile };
|
|
173
|
+
} else if (type === 'camera') {
|
|
174
|
+
const result = await launchCamera({ mediaType: 'photo' });
|
|
175
|
+
if (!result.assets || result.assets.length === 0) return;
|
|
176
|
+
replacedFile = { ...result.assets[0], labelFile: files[index].labelFile };
|
|
177
|
+
} else {
|
|
178
|
+
const result = await pick({ type: accept, multiple });
|
|
179
|
+
if (!result || result.length === 0) return;
|
|
180
|
+
replacedFile = { ...result[0], labelFile: files[index].labelFile };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const updatedFiles = files.map((f, i) => (i === index ? replacedFile : f));
|
|
184
|
+
|
|
185
|
+
setFiles(updatedFiles);
|
|
186
|
+
onChange?.(updatedFiles);
|
|
187
|
+
setSelectedFileIndex(null);
|
|
188
|
+
setIsOpenBottomSheetTypeFile(false);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
setFiles(value ?? []);
|
|
193
|
+
}, [value]);
|
|
194
|
+
|
|
111
195
|
return (
|
|
112
196
|
<View style={styles.wrapper}>
|
|
113
197
|
<Card style={styles.containerInput}>
|
|
@@ -121,50 +205,103 @@ export default function InputFile({
|
|
|
121
205
|
</Typography>
|
|
122
206
|
<Button
|
|
123
207
|
color="primary"
|
|
124
|
-
title=
|
|
208
|
+
title={btnChooseFileText}
|
|
209
|
+
size="small"
|
|
125
210
|
onPress={handleOnPresChoseFile}
|
|
126
211
|
/>
|
|
127
212
|
</Center>
|
|
128
213
|
</Card>
|
|
129
214
|
|
|
130
215
|
{files.map((file, index) => (
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
216
|
+
<View style={styles.previewWrapper} key={index}>
|
|
217
|
+
{useChangeLabel && (
|
|
218
|
+
<Input
|
|
219
|
+
label="Nama Dokumen #1"
|
|
220
|
+
placeholder="Masukkan Nama Dokumen"
|
|
221
|
+
value={file.labelFile}
|
|
222
|
+
onChangeText={(text) => {
|
|
223
|
+
const updatedFiles = files.map((f, i) =>
|
|
224
|
+
i === index ? { ...f, labelFile: text } : f
|
|
225
|
+
);
|
|
226
|
+
setFiles(updatedFiles);
|
|
227
|
+
onChange?.(updatedFiles);
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
)}
|
|
231
|
+
<View style={styles.gap4}>
|
|
232
|
+
<Card>
|
|
233
|
+
<TouchableOpacity onPress={() => handlePreviewFile(file)}>
|
|
234
|
+
<View style={styles.containerPreview}>
|
|
235
|
+
<View style={[styles.containerListItem, styles.flex1]}>
|
|
236
|
+
{file.type.startsWith('image') ? (
|
|
237
|
+
<Image
|
|
238
|
+
source={
|
|
239
|
+
file.uri?.endsWith('.pdf')
|
|
240
|
+
? require('../Input/assets/input-file.png')
|
|
241
|
+
: { uri: file.uri }
|
|
242
|
+
}
|
|
243
|
+
style={styles.previewImage}
|
|
244
|
+
/>
|
|
245
|
+
) : (
|
|
246
|
+
<View style={styles.previewImage}>
|
|
247
|
+
<Icon name="Document" color={Color.primary[1000]} />
|
|
248
|
+
</View>
|
|
249
|
+
)}
|
|
250
|
+
<View style={styles.fileDetails}>
|
|
251
|
+
<Typography
|
|
252
|
+
variant="t1"
|
|
253
|
+
weight="semibold"
|
|
254
|
+
color={Color.gray[600]}
|
|
255
|
+
numberOfLines={1}
|
|
256
|
+
>
|
|
257
|
+
{file.name || `Image_${index + 1}`}
|
|
258
|
+
</Typography>
|
|
259
|
+
<Typography
|
|
260
|
+
variant="t2"
|
|
261
|
+
weight="regular"
|
|
262
|
+
color={Color.gray[600]}
|
|
263
|
+
>
|
|
264
|
+
{(
|
|
265
|
+
(file?.size ?? file?.fileSize ?? 0) /
|
|
266
|
+
(1024 * 1024)
|
|
267
|
+
).toFixed(2)}
|
|
268
|
+
MB
|
|
269
|
+
</Typography>
|
|
270
|
+
</View>
|
|
271
|
+
</View>
|
|
272
|
+
<View style={styles.action}>
|
|
273
|
+
{file.error && (
|
|
274
|
+
<Icon
|
|
275
|
+
name="exclamation-triangle"
|
|
276
|
+
color={Color.danger[500]}
|
|
277
|
+
size={20}
|
|
278
|
+
/>
|
|
279
|
+
)}
|
|
280
|
+
<TouchableOpacity onPress={() => handleReplaceFile(index)}>
|
|
281
|
+
<Icon
|
|
282
|
+
name="rotate-right"
|
|
283
|
+
size={18}
|
|
284
|
+
color={Color.gray[700]}
|
|
285
|
+
/>
|
|
286
|
+
</TouchableOpacity>
|
|
287
|
+
<TouchableOpacity onPress={() => confirmDeleteFile(index)}>
|
|
288
|
+
<Icon name="Times" size={12} color={Color.gray[700]} />
|
|
289
|
+
</TouchableOpacity>
|
|
290
|
+
</View>
|
|
158
291
|
</View>
|
|
159
|
-
</
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
292
|
+
</TouchableOpacity>
|
|
293
|
+
</Card>
|
|
294
|
+
|
|
295
|
+
{file.hint && (
|
|
296
|
+
<Typography
|
|
297
|
+
variant="t3"
|
|
298
|
+
color={!file.error ? Color.gray[700] : Color.danger[500]}
|
|
299
|
+
>
|
|
300
|
+
{file.hint}
|
|
301
|
+
</Typography>
|
|
302
|
+
)}
|
|
303
|
+
</View>
|
|
304
|
+
</View>
|
|
168
305
|
))}
|
|
169
306
|
|
|
170
307
|
<BottomSheet
|
|
@@ -174,10 +311,10 @@ export default function InputFile({
|
|
|
174
311
|
<View style={styles.bottomSheetContent}>
|
|
175
312
|
<Center style={styles.bottomSheetCenter}>
|
|
176
313
|
<Typography variant="p2" weight="semibold" color={Color.gray[800]}>
|
|
177
|
-
Upload Dokumen
|
|
314
|
+
{modalPickFileText?.title || 'Upload Dokumen'}
|
|
178
315
|
</Typography>
|
|
179
|
-
<Typography variant="t1" weight="regular" color={Color.gray[
|
|
180
|
-
Unggah dokumen maksimal
|
|
316
|
+
<Typography variant="t1" weight="regular" color={Color.gray[600]}>
|
|
317
|
+
{modalPickFileText?.description || 'Unggah dokumen maksimal'}
|
|
181
318
|
</Typography>
|
|
182
319
|
</Center>
|
|
183
320
|
<List>
|
|
@@ -191,10 +328,11 @@ export default function InputFile({
|
|
|
191
328
|
weight="semibold"
|
|
192
329
|
color={Color.gray[800]}
|
|
193
330
|
>
|
|
194
|
-
Ambil Foto
|
|
331
|
+
{modalPickFileText?.camera?.title || 'Ambil Foto'}
|
|
195
332
|
</Typography>
|
|
196
333
|
<Typography variant="t2" color={Color.gray[600]}>
|
|
197
|
-
|
|
334
|
+
{modalPickFileText?.camera?.description ||
|
|
335
|
+
'Langsung ambil gambar dari kamera'}
|
|
198
336
|
</Typography>
|
|
199
337
|
</View>
|
|
200
338
|
</View>
|
|
@@ -210,10 +348,11 @@ export default function InputFile({
|
|
|
210
348
|
weight="semibold"
|
|
211
349
|
color={Color.gray[800]}
|
|
212
350
|
>
|
|
213
|
-
Pilih dari Galeri
|
|
351
|
+
{modalPickFileText?.gallery?.title || 'Pilih dari Galeri'}
|
|
214
352
|
</Typography>
|
|
215
353
|
<Typography variant="t2" color={Color.gray[600]}>
|
|
216
|
-
|
|
354
|
+
{modalPickFileText?.gallery?.description ||
|
|
355
|
+
'Cari gambar dari galeri perangkatmu'}
|
|
217
356
|
</Typography>
|
|
218
357
|
</View>
|
|
219
358
|
</View>
|
|
@@ -229,10 +368,11 @@ export default function InputFile({
|
|
|
229
368
|
weight="semibold"
|
|
230
369
|
color={Color.gray[800]}
|
|
231
370
|
>
|
|
232
|
-
Pilih
|
|
371
|
+
{modalPickFileText?.document?.title || 'Pilih Dokumen'}
|
|
233
372
|
</Typography>
|
|
234
373
|
<Typography variant="t2" color={Color.gray[600]}>
|
|
235
|
-
|
|
374
|
+
{modalPickFileText?.document?.description ||
|
|
375
|
+
'Pilih dokumen dari perangkatmu'}
|
|
236
376
|
</Typography>
|
|
237
377
|
</View>
|
|
238
378
|
</View>
|
|
@@ -248,23 +388,34 @@ export default function InputFile({
|
|
|
248
388
|
>
|
|
249
389
|
<View style={styles.deleteFileContent}>
|
|
250
390
|
<Center style={styles.deleteFileCenter}>
|
|
251
|
-
<Typography
|
|
252
|
-
|
|
391
|
+
<Typography
|
|
392
|
+
variant="p2"
|
|
393
|
+
weight="semibold"
|
|
394
|
+
color={Color.gray[800]}
|
|
395
|
+
center
|
|
396
|
+
>
|
|
397
|
+
{modalDeleteText?.title || 'Hapus Dokumen'}
|
|
253
398
|
</Typography>
|
|
254
|
-
<Typography
|
|
255
|
-
|
|
399
|
+
<Typography
|
|
400
|
+
variant="t1"
|
|
401
|
+
weight="regular"
|
|
402
|
+
color={Color.gray[500]}
|
|
403
|
+
center
|
|
404
|
+
>
|
|
405
|
+
{modalDeleteText?.description ||
|
|
406
|
+
'Apakah kamu akan menghapus dokumen yang dipilih?'}
|
|
256
407
|
</Typography>
|
|
257
408
|
</Center>
|
|
258
409
|
<View>
|
|
259
410
|
<Button
|
|
260
411
|
color="danger"
|
|
261
|
-
title=
|
|
412
|
+
title={modalDeleteText?.confirmBtn?.confirm || 'Hapus'}
|
|
262
413
|
onPress={deleteFile}
|
|
263
414
|
/>
|
|
264
415
|
<Button
|
|
265
416
|
color="primary"
|
|
266
417
|
variant="tertiary"
|
|
267
|
-
title=
|
|
418
|
+
title={modalDeleteText?.confirmBtn?.cancel || 'Batal'}
|
|
268
419
|
onPress={() => setIsOpenBottomSheetDeleteFile(false)}
|
|
269
420
|
/>
|
|
270
421
|
</View>
|
|
@@ -280,7 +431,8 @@ const styles = StyleSheet.create({
|
|
|
280
431
|
},
|
|
281
432
|
containerInput: {
|
|
282
433
|
borderStyle: 'dashed',
|
|
283
|
-
backgroundColor: Color.gray[
|
|
434
|
+
backgroundColor: Color.gray[50],
|
|
435
|
+
borderRadius: 16,
|
|
284
436
|
},
|
|
285
437
|
centerInput: {
|
|
286
438
|
gap: 14,
|
|
@@ -295,14 +447,18 @@ const styles = StyleSheet.create({
|
|
|
295
447
|
width: 44,
|
|
296
448
|
height: 44,
|
|
297
449
|
borderRadius: 6,
|
|
450
|
+
alignItems: 'center',
|
|
451
|
+
justifyContent: 'center',
|
|
298
452
|
},
|
|
299
453
|
fileDetails: {
|
|
300
454
|
justifyContent: 'center',
|
|
301
|
-
gap:
|
|
455
|
+
gap: 4,
|
|
456
|
+
flex: 1,
|
|
302
457
|
},
|
|
303
458
|
containerListItem: {
|
|
304
459
|
flexDirection: 'row',
|
|
305
460
|
gap: 8,
|
|
461
|
+
alignItems: 'center',
|
|
306
462
|
},
|
|
307
463
|
bottomSheetContent: {
|
|
308
464
|
gap: 16,
|
|
@@ -317,4 +473,19 @@ const styles = StyleSheet.create({
|
|
|
317
473
|
alignItems: 'center',
|
|
318
474
|
gap: 4,
|
|
319
475
|
},
|
|
476
|
+
action: {
|
|
477
|
+
flexDirection: 'row',
|
|
478
|
+
alignItems: 'center',
|
|
479
|
+
gap: 12,
|
|
480
|
+
flexShrink: 0,
|
|
481
|
+
},
|
|
482
|
+
previewWrapper: {
|
|
483
|
+
gap: 12,
|
|
484
|
+
},
|
|
485
|
+
flex1: {
|
|
486
|
+
flex: 1,
|
|
487
|
+
},
|
|
488
|
+
gap4: {
|
|
489
|
+
gap: 4,
|
|
490
|
+
},
|
|
320
491
|
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# 📂 InputFile Component
|
|
2
|
+
|
|
3
|
+
Komponen `InputFile` digunakan untuk mengunggah, menampilkan, mengganti, menghapus, serta memberi label pada dokumen atau gambar di aplikasi React Native.
|
|
4
|
+
Mendukung **camera**, **gallery**, dan **document picker** dengan tampilan preview dan konfirmasi aksi.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## ✨ Fitur
|
|
9
|
+
- Upload file dari **kamera**, **galeri**, atau **dokumen perangkat**.
|
|
10
|
+
- Preview file (gambar atau dokumen).
|
|
11
|
+
- Mendukung **multiple file upload**.
|
|
12
|
+
- Ganti file (replace) atau hapus file dengan konfirmasi.
|
|
13
|
+
- Tambahkan label/nama dokumen secara manual.
|
|
14
|
+
- Konfigurasi teks modal (custom title, description, button).
|
|
15
|
+
- Callback `onChange` untuk mengirim data file terbaru ke parent.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 📦 Dependencies
|
|
20
|
+
Komponen ini membutuhkan beberapa library:
|
|
21
|
+
- [`@react-native-documents/picker`](https://github.com/react-native-documents/picker)
|
|
22
|
+
- [`@react-native-documents/viewer`](https://github.com/react-native-documents/viewer)
|
|
23
|
+
- [`react-native-image-picker`](https://github.com/react-native-image-picker/react-native-image-picker)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Penggunaan
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import InputFile from "@/components/Input/InputFile";
|
|
31
|
+
import { useState } from "react";
|
|
32
|
+
|
|
33
|
+
export default function ExampleScreen() {
|
|
34
|
+
const [files, setFiles] = useState<any[]>([]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<InputFile
|
|
38
|
+
title="Upload Dokumen"
|
|
39
|
+
description="Format file: JPG, PNG, PDF"
|
|
40
|
+
btnChooseFileText="Pilih File"
|
|
41
|
+
multiple={true}
|
|
42
|
+
value={files}
|
|
43
|
+
useChangeLabel={true}
|
|
44
|
+
modalPickFileText={{
|
|
45
|
+
title: "Pilih Sumber",
|
|
46
|
+
description: "Unggah dokumen atau ambil foto",
|
|
47
|
+
camera: { title: "Kamera", description: "Ambil foto langsung" },
|
|
48
|
+
gallery: { title: "Galeri", description: "Pilih gambar dari galeri" },
|
|
49
|
+
document: { title: "Dokumen", description: "Pilih file dari perangkat" },
|
|
50
|
+
}}
|
|
51
|
+
modalDeleteText={{
|
|
52
|
+
title: "Hapus File?",
|
|
53
|
+
description: "Apakah kamu yakin ingin menghapus file ini?",
|
|
54
|
+
confirmBtn: {
|
|
55
|
+
confirm: "Ya, Hapus",
|
|
56
|
+
cancel: "Batal",
|
|
57
|
+
},
|
|
58
|
+
}}
|
|
59
|
+
onChange={(newFiles) => setFiles(newFiles)}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## ⚙️ Props
|
|
68
|
+
|
|
69
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
70
|
+
|--------------------|-----------------------------------|-----------------------------------------------------------|---------------------------------------------------------------------------|
|
|
71
|
+
| `title` | `string` | `"Upload File"` | Judul input file. |
|
|
72
|
+
| `description` | `string` | `"File harus berformat JPG, PNG dan PDF"` | Deskripsi singkat tentang file yang diterima. |
|
|
73
|
+
| `accept` | `string[]` | `["application/pdf", "image/jpeg", "image/png"]` | Tipe MIME file yang diizinkan. |
|
|
74
|
+
| `multiple` | `boolean` | `false` | Jika `true`, memungkinkan memilih banyak file sekaligus. |
|
|
75
|
+
| `value` | `any[]` | `[]` | Daftar file yang sudah dipilih (state dari parent). |
|
|
76
|
+
| `onChange` | `(files: any[]) => void` | `undefined` | Callback saat daftar file berubah (add, replace, delete). |
|
|
77
|
+
| `btnChooseFileText`| `string` | `"Choose File"` | Label tombol untuk memilih file. |
|
|
78
|
+
| `modalPickFileText`| `modalPickFileText` (lihat bawah) | `undefined` | Teks kustom pada modal pemilihan sumber file. |
|
|
79
|
+
| `modalDeleteText` | `ModalOption & confirmBtn` | `undefined` | Teks kustom pada modal konfirmasi hapus file. |
|
|
80
|
+
| `useChangeLabel` | `boolean` | `false` | Jika `true`, menampilkan input untuk memberi nama/label pada file. |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 🔤 Tipe Tambahan
|
|
85
|
+
|
|
86
|
+
### `ModalOption`
|
|
87
|
+
```ts
|
|
88
|
+
interface ModalOption {
|
|
89
|
+
title?: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `modalPickFileText`
|
|
95
|
+
```ts
|
|
96
|
+
interface modalPickFileText {
|
|
97
|
+
title?: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
camera?: ModalOption;
|
|
100
|
+
gallery?: ModalOption;
|
|
101
|
+
document?: ModalOption;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `modalDeleteText`
|
|
106
|
+
```ts
|
|
107
|
+
interface ModalOption {
|
|
108
|
+
title?: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
confirmBtn?: {
|
|
111
|
+
confirm?: string;
|
|
112
|
+
cancel?: string;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 📸 Preview UI
|
|
120
|
+
- **Card input awal** → tombol pilih file + icon ilustrasi.
|
|
121
|
+
- **Preview file** → menampilkan nama, ukuran, icon (atau thumbnail jika image).
|
|
122
|
+
- **Aksi file**:
|
|
123
|
+
- 🔄 Replace
|
|
124
|
+
- ❌ Delete
|
|
125
|
+
- 👁 Preview (buka dokumen dengan `viewDocument`)
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 🛠 Catatan
|
|
130
|
+
1. Pastikan permission kamera & storage sudah diatur (Android/iOS).
|
|
131
|
+
2. File hasil picker akan memiliki property standar (`uri`, `type`, `name`, `size`).
|
|
132
|
+
3. Untuk **multiple upload**, `value` berupa array file yang akan dikelola oleh parent.
|
package/src/Ui/Container.tsx
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { StyleSheet, View } from 'react-native';
|
|
1
|
+
import { StyleSheet, View, type ViewStyle } from 'react-native';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
interface Props {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
style?: ViewStyle;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function Container({ children, style }: Props) {
|
|
4
9
|
return <View style={[styles.container, style && style]}>{children}</View>;
|
|
5
10
|
}
|
|
6
11
|
|