@herca/rn-kit 0.1.69 → 0.1.70
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/Input/InputFile.js +76 -275
- package/lib/module/Input/InputFile.js.map +1 -1
- package/lib/module/Input/README_INPUT_FILE.md +144 -23
- package/lib/module/Input/partials/InputFile/CardTrigger.js +77 -0
- package/lib/module/Input/partials/InputFile/CardTrigger.js.map +1 -0
- package/lib/module/Input/partials/InputFile/CardTriggerSmall.js +86 -0
- package/lib/module/Input/partials/InputFile/CardTriggerSmall.js.map +1 -0
- package/lib/module/Input/partials/InputFile/ItemPicker.js +43 -0
- package/lib/module/Input/partials/InputFile/ItemPicker.js.map +1 -0
- package/lib/module/Input/partials/InputFile/ItemPreview.js +149 -0
- package/lib/module/Input/partials/InputFile/ItemPreview.js.map +1 -0
- package/lib/module/Input/partials/InputFile/ModalDelete.js +76 -0
- package/lib/module/Input/partials/InputFile/ModalDelete.js.map +1 -0
- package/lib/module/Input/partials/InputFile/ModalPicker.js +72 -0
- package/lib/module/Input/partials/InputFile/ModalPicker.js.map +1 -0
- package/lib/module/Ui/Center.js.map +1 -1
- package/lib/module/styles/layouting.js +3 -0
- package/lib/module/styles/layouting.js.map +1 -1
- package/lib/typescript/src/Input/InputFile.d.ts +17 -4
- package/lib/typescript/src/Input/InputFile.d.ts.map +1 -1
- package/lib/typescript/src/Input/partials/InputFile/CardTrigger.d.ts +11 -0
- package/lib/typescript/src/Input/partials/InputFile/CardTrigger.d.ts.map +1 -0
- package/lib/typescript/src/Input/partials/InputFile/CardTriggerSmall.d.ts +12 -0
- package/lib/typescript/src/Input/partials/InputFile/CardTriggerSmall.d.ts.map +1 -0
- package/lib/typescript/src/Input/partials/InputFile/ItemPicker.d.ts +10 -0
- package/lib/typescript/src/Input/partials/InputFile/ItemPicker.d.ts.map +1 -0
- package/lib/typescript/src/Input/partials/InputFile/ItemPreview.d.ts +11 -0
- package/lib/typescript/src/Input/partials/InputFile/ItemPreview.d.ts.map +1 -0
- package/lib/typescript/src/Input/partials/InputFile/ModalDelete.d.ts +12 -0
- package/lib/typescript/src/Input/partials/InputFile/ModalDelete.d.ts.map +1 -0
- package/lib/typescript/src/Input/partials/InputFile/ModalPicker.d.ts +18 -0
- package/lib/typescript/src/Input/partials/InputFile/ModalPicker.d.ts.map +1 -0
- package/lib/typescript/src/Ui/Center.d.ts +2 -2
- package/lib/typescript/src/Ui/Center.d.ts.map +1 -1
- package/lib/typescript/src/styles/layouting.d.ts +3 -0
- package/lib/typescript/src/styles/layouting.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Input/InputFile.tsx +148 -296
- package/src/Input/README_INPUT_FILE.md +144 -23
- package/src/Input/partials/InputFile/CardTrigger.tsx +87 -0
- package/src/Input/partials/InputFile/CardTriggerSmall.tsx +120 -0
- package/src/Input/partials/InputFile/ItemPicker.tsx +46 -0
- package/src/Input/partials/InputFile/ItemPreview.tsx +182 -0
- package/src/Input/partials/InputFile/ModalDelete.tsx +84 -0
- package/src/Input/partials/InputFile/ModalPicker.tsx +86 -0
- package/src/Ui/Center.tsx +2 -2
- package/src/styles/layouting.ts +1 -0
|
@@ -11,7 +11,8 @@ Mendukung **camera**, **gallery**, dan **document picker** dengan tampilan previ
|
|
|
11
11
|
- Preview file (gambar atau dokumen).
|
|
12
12
|
- Mendukung **multiple file upload**.
|
|
13
13
|
- Ganti file (replace) atau hapus file dengan konfirmasi.
|
|
14
|
-
- Tambahkan label/nama dokumen secara manual.
|
|
14
|
+
- Tambahkan label/nama dokumen secara manual (hanya untuk variant `default`).
|
|
15
|
+
- **2 Variant tampilan**: `default` (full) dan `small` (compact).
|
|
15
16
|
- Konfigurasi teks modal (custom title, description, button).
|
|
16
17
|
- Callback `onChange` untuk mengirim data file terbaru ke parent.
|
|
17
18
|
|
|
@@ -29,6 +30,8 @@ Komponen ini membutuhkan beberapa library:
|
|
|
29
30
|
|
|
30
31
|
## 🚀 Penggunaan
|
|
31
32
|
|
|
33
|
+
### Variant Default (Full Features)
|
|
34
|
+
|
|
32
35
|
```tsx
|
|
33
36
|
import InputFile from '@/components/Input/InputFile';
|
|
34
37
|
import { useState } from 'react';
|
|
@@ -38,12 +41,19 @@ export default function ExampleScreen() {
|
|
|
38
41
|
|
|
39
42
|
return (
|
|
40
43
|
<InputFile
|
|
44
|
+
variant="default" // atau bisa dihilangkan (default)
|
|
41
45
|
title="Upload Dokumen"
|
|
42
46
|
description="Format file: JPG, PNG, PDF"
|
|
47
|
+
hint="Maksimal ukuran file 5MB"
|
|
48
|
+
hasError={false}
|
|
43
49
|
btnChooseFileText="Pilih File"
|
|
44
50
|
multiple={true}
|
|
45
51
|
value={files}
|
|
46
52
|
useChangeLabel={true}
|
|
53
|
+
changeLableProps={{
|
|
54
|
+
label: 'Nama Dokumen',
|
|
55
|
+
placeholder: 'Masukkan nama dokumen',
|
|
56
|
+
}}
|
|
47
57
|
modalPickFileText={{
|
|
48
58
|
title: 'Pilih Sumber',
|
|
49
59
|
description: 'Unggah dokumen atau ambil foto',
|
|
@@ -68,23 +78,67 @@ export default function ExampleScreen() {
|
|
|
68
78
|
}
|
|
69
79
|
```
|
|
70
80
|
|
|
81
|
+
### Variant Small (Compact)
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import InputFile from '@/components/Input/InputFile';
|
|
85
|
+
import { useState } from 'react';
|
|
86
|
+
|
|
87
|
+
export default function ExampleScreen() {
|
|
88
|
+
const [files, setFiles] = useState<any[]>([]);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<InputFile
|
|
92
|
+
variant="small"
|
|
93
|
+
title="Upload KTP"
|
|
94
|
+
btnChooseFileText="Pilih"
|
|
95
|
+
multiple={false}
|
|
96
|
+
value={files}
|
|
97
|
+
hasError={false}
|
|
98
|
+
onChange={(newFiles) => setFiles(newFiles)}
|
|
99
|
+
// ❌ description, hint, useChangeLabel, changeLableProps TIDAK BISA dipakai di variant small
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
71
105
|
---
|
|
72
106
|
|
|
73
107
|
## ⚙️ Props
|
|
74
108
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
109
|
+
### Base Props (Tersedia di Semua Variant)
|
|
110
|
+
|
|
111
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
112
|
+
| ------------------- | --------------------------------- | ------------------------------------------------ | --------------------------------------------------------- |
|
|
113
|
+
| `title` | `string` | `"Upload File"` | Judul input file. |
|
|
114
|
+
| `accept` | `string[]` | `["application/pdf", "image/jpeg", "image/png"]` | Tipe MIME file yang diizinkan. |
|
|
115
|
+
| `multiple` | `boolean` | `false` | Jika `true`, memungkinkan memilih banyak file sekaligus. |
|
|
116
|
+
| `value` | `any[]` | `[]` | Daftar file yang sudah dipilih (state dari parent). |
|
|
117
|
+
| `onChange` | `(files: any[]) => void` | `undefined` | Callback saat daftar file berubah (add, replace, delete). |
|
|
118
|
+
| `btnChooseFileText` | `string` | `"Choose File"` | Label tombol untuk memilih file. |
|
|
119
|
+
| `modalPickFileText` | `ModalPickFileText` (lihat bawah) | `undefined` | Teks kustom pada modal pemilihan sumber file. |
|
|
120
|
+
| `modalDeleteText` | `ModalOption & confirmBtn` | `undefined` | Teks kustom pada modal konfirmasi hapus file. |
|
|
121
|
+
| `hasError` | `boolean` | `false` | Menampilkan state error pada input. |
|
|
122
|
+
|
|
123
|
+
### Variant-Specific Props
|
|
124
|
+
|
|
125
|
+
#### Default Variant (`variant?: 'default'`)
|
|
126
|
+
|
|
127
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
128
|
+
| ------------------ | -------------------------------- | ----------------------------------------- | ------------------------------------------------------------ |
|
|
129
|
+
| `variant` | `'default'` | `'default'` | Variant default dengan fitur lengkap. |
|
|
130
|
+
| `description` | `string` | `"File harus berformat JPG, PNG dan PDF"` | Deskripsi singkat tentang file yang diterima. |
|
|
131
|
+
| `hint` | `string` | `undefined` | Teks hint tambahan di bawah input. |
|
|
132
|
+
| `useChangeLabel` | `boolean` | `false` | Jika `true`, menampilkan input untuk memberi nama pada file. |
|
|
133
|
+
| `changeLableProps` | `ChangeLabelProps` (lihat bawah) | `undefined` | Teks kustom pada input label di atas preview file. |
|
|
134
|
+
|
|
135
|
+
#### Small Variant (`variant: 'small'`)
|
|
136
|
+
|
|
137
|
+
| Prop | Tipe | Default | Deskripsi |
|
|
138
|
+
| --------- | --------- | ------- | ------------------------------------- |
|
|
139
|
+
| `variant` | `'small'` | - | Variant compact tanpa fitur tambahan. |
|
|
140
|
+
|
|
141
|
+
> ⚠️ **Catatan**: Props `description`, `hint`, `useChangeLabel`, dan `changeLableProps` **TIDAK TERSEDIA** di variant `small`. TypeScript akan menampilkan error jika props ini digunakan dengan `variant: 'small'`.
|
|
88
142
|
|
|
89
143
|
---
|
|
90
144
|
|
|
@@ -99,7 +153,7 @@ interface ModalOption {
|
|
|
99
153
|
}
|
|
100
154
|
```
|
|
101
155
|
|
|
102
|
-
### `
|
|
156
|
+
### `ModalPickFileText`
|
|
103
157
|
|
|
104
158
|
```ts
|
|
105
159
|
interface ModalPickFileText {
|
|
@@ -111,20 +165,18 @@ interface ModalPickFileText {
|
|
|
111
165
|
}
|
|
112
166
|
```
|
|
113
167
|
|
|
114
|
-
### `
|
|
168
|
+
### `ModalDeleteText`
|
|
115
169
|
|
|
116
170
|
```ts
|
|
117
|
-
|
|
118
|
-
title?: string;
|
|
119
|
-
description?: string;
|
|
171
|
+
type ModalDeleteText = ModalOption & {
|
|
120
172
|
confirmBtn?: {
|
|
121
173
|
confirm?: string;
|
|
122
174
|
cancel?: string;
|
|
123
175
|
};
|
|
124
|
-
}
|
|
176
|
+
};
|
|
125
177
|
```
|
|
126
178
|
|
|
127
|
-
### `
|
|
179
|
+
### `ChangeLabelProps`
|
|
128
180
|
|
|
129
181
|
```ts
|
|
130
182
|
interface ChangeLabelProps {
|
|
@@ -135,15 +187,81 @@ interface ChangeLabelProps {
|
|
|
135
187
|
|
|
136
188
|
---
|
|
137
189
|
|
|
138
|
-
##
|
|
190
|
+
## 📐 Type Definitions
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
export interface ModalOption {
|
|
194
|
+
title?: string;
|
|
195
|
+
description?: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface ModalPickFileText {
|
|
199
|
+
title?: string;
|
|
200
|
+
description?: string;
|
|
201
|
+
camera?: ModalOption;
|
|
202
|
+
gallery?: ModalOption;
|
|
203
|
+
document?: ModalOption;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface ChangeLabelProps {
|
|
207
|
+
label?: string;
|
|
208
|
+
placeholder?: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
type BaseInputFileProps = {
|
|
212
|
+
title?: string;
|
|
213
|
+
accept?: string[];
|
|
214
|
+
multiple?: boolean;
|
|
215
|
+
onChange?: (files: any) => void;
|
|
216
|
+
value?: any[];
|
|
217
|
+
modalPickFileText?: ModalPickFileText;
|
|
218
|
+
btnChooseFileText?: string;
|
|
219
|
+
modalDeleteText?: ModalOption & {
|
|
220
|
+
confirmBtn?: {
|
|
221
|
+
confirm?: string;
|
|
222
|
+
cancel?: string;
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
hasError?: boolean;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
type DefaultVariantProps = BaseInputFileProps & {
|
|
229
|
+
variant?: 'default';
|
|
230
|
+
description?: string;
|
|
231
|
+
useChangeLabel?: boolean;
|
|
232
|
+
changeLableProps?: ChangeLabelProps;
|
|
233
|
+
hint?: string;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
type SmallVariantProps = BaseInputFileProps & {
|
|
237
|
+
variant: 'small';
|
|
238
|
+
description?: never;
|
|
239
|
+
useChangeLabel?: never;
|
|
240
|
+
changeLableProps?: never;
|
|
241
|
+
hint?: never;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
type InputFileProps = DefaultVariantProps | SmallVariantProps;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### Variant Default
|
|
139
250
|
|
|
140
|
-
- **Card input awal** → tombol pilih file + icon ilustrasi.
|
|
251
|
+
- **Card input awal** → tombol pilih file + icon ilustrasi + description + hint.
|
|
141
252
|
- **Preview file** → menampilkan nama, ukuran, icon (atau thumbnail jika image).
|
|
253
|
+
- **Input label** (jika `useChangeLabel: true`) → input text untuk memberi nama dokumen.
|
|
142
254
|
- **Aksi file**:
|
|
143
255
|
- 🔄 Replace
|
|
144
256
|
- ❌ Delete
|
|
145
257
|
- 👁 Preview (buka dokumen dengan `viewDocument`)
|
|
146
258
|
|
|
259
|
+
### Variant Small
|
|
260
|
+
|
|
261
|
+
- **Compact card** → tombol pilih file dengan tampilan lebih ringkas.
|
|
262
|
+
- **Preview file** → tampilan minimal tanpa label/hint tambahan.
|
|
263
|
+
- **Aksi file**: sama seperti variant default (replace, delete, preview).
|
|
264
|
+
|
|
147
265
|
---
|
|
148
266
|
|
|
149
267
|
## 🛠 Catatan
|
|
@@ -151,3 +269,6 @@ interface ChangeLabelProps {
|
|
|
151
269
|
1. Pastikan permission kamera & storage sudah diatur (Android/iOS).
|
|
152
270
|
2. File hasil picker akan memiliki property standar (`uri`, `type`, `name`, `size`).
|
|
153
271
|
3. Untuk **multiple upload**, `value` berupa array file yang akan dikelola oleh parent.
|
|
272
|
+
4. **Variant `small`** cocok untuk form yang membutuhkan tampilan compact tanpa deskripsi dan hint.
|
|
273
|
+
5. **Variant `default`** cocok untuk form yang membutuhkan instruksi detail dan fitur labeling dokumen.
|
|
274
|
+
6. TypeScript akan memberikan error compile-time jika props yang tidak sesuai variant digunakan.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Image, StyleSheet, View } from 'react-native';
|
|
2
|
+
import Button from '../../../Button/Button';
|
|
3
|
+
import Typography from '../../../Typography/Typography';
|
|
4
|
+
import Card from '../../../Ui/Card';
|
|
5
|
+
import Center from '../../../Ui/Center';
|
|
6
|
+
import Color from '../../../Color/Color';
|
|
7
|
+
import { spacing } from '../../../styles/spacing';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
btnSelect?: string;
|
|
13
|
+
onPress?: () => void;
|
|
14
|
+
hasError?: boolean;
|
|
15
|
+
hint?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function CardTrigger({
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
btnSelect,
|
|
22
|
+
onPress,
|
|
23
|
+
hint,
|
|
24
|
+
hasError,
|
|
25
|
+
}: Props) {
|
|
26
|
+
return (
|
|
27
|
+
<Card style={styles.containerInput}>
|
|
28
|
+
<Center style={styles.centerInput}>
|
|
29
|
+
<Image source={require('../../assets/input-file.png')} />
|
|
30
|
+
<View style={spacing.gap[4]}>
|
|
31
|
+
<Typography
|
|
32
|
+
variant="t1"
|
|
33
|
+
weight="semibold"
|
|
34
|
+
color={Color.gray[500]}
|
|
35
|
+
center
|
|
36
|
+
>
|
|
37
|
+
{title}
|
|
38
|
+
</Typography>
|
|
39
|
+
{hint && (
|
|
40
|
+
<Typography
|
|
41
|
+
variant="t3"
|
|
42
|
+
weight="regular"
|
|
43
|
+
color={hasError ? Color.danger[500] : Color.gray[500]}
|
|
44
|
+
center
|
|
45
|
+
>
|
|
46
|
+
{hint}
|
|
47
|
+
</Typography>
|
|
48
|
+
)}
|
|
49
|
+
<Typography variant="t3" weight="regular" color={Color.gray[500]}>
|
|
50
|
+
{description}
|
|
51
|
+
</Typography>
|
|
52
|
+
</View>
|
|
53
|
+
<Button
|
|
54
|
+
color="primary"
|
|
55
|
+
title={btnSelect}
|
|
56
|
+
size="small"
|
|
57
|
+
onPress={onPress}
|
|
58
|
+
/>
|
|
59
|
+
</Center>
|
|
60
|
+
</Card>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const styles = StyleSheet.create({
|
|
65
|
+
wrapper: {
|
|
66
|
+
gap: 12,
|
|
67
|
+
},
|
|
68
|
+
containerInput: {
|
|
69
|
+
borderStyle: 'dashed',
|
|
70
|
+
backgroundColor: Color.gray[50],
|
|
71
|
+
borderRadius: 16,
|
|
72
|
+
},
|
|
73
|
+
centerInput: {
|
|
74
|
+
gap: 14,
|
|
75
|
+
},
|
|
76
|
+
deleteFileContent: {
|
|
77
|
+
gap: 10,
|
|
78
|
+
},
|
|
79
|
+
deleteFileCenter: {
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
gap: 4,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
previewWrapper: {
|
|
85
|
+
gap: 12,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Image,
|
|
3
|
+
ScrollView,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
View,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import Card from '../../../Ui/Card';
|
|
9
|
+
import Color from '../../../Color/Color';
|
|
10
|
+
import Typography from '../../../Typography/Typography';
|
|
11
|
+
import Button from '../../../Button/Button';
|
|
12
|
+
import { layouting } from '../../../styles/layouting';
|
|
13
|
+
import ItemPreview from './ItemPreview';
|
|
14
|
+
import { spacing } from '../../../styles/spacing';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
title?: string;
|
|
18
|
+
textButton?: string;
|
|
19
|
+
files: any[];
|
|
20
|
+
onChooseFile?: () => void;
|
|
21
|
+
onPreview?: (file: any) => void;
|
|
22
|
+
onReplace?: (index: number) => void;
|
|
23
|
+
onDelete?: (index: number) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function CardTriggerSmall({
|
|
27
|
+
title = 'Upload File',
|
|
28
|
+
textButton = 'Choose File',
|
|
29
|
+
onChooseFile,
|
|
30
|
+
files,
|
|
31
|
+
onDelete,
|
|
32
|
+
onPreview,
|
|
33
|
+
onReplace,
|
|
34
|
+
}: Props) {
|
|
35
|
+
const Component = files?.length === 0 ? View : TouchableOpacity;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<View style={[layouting.flex.row, spacing.gap[8]]}>
|
|
39
|
+
<Card
|
|
40
|
+
style={[styles.card, files.length === 0 ? layouting.flex.grow : {}]}
|
|
41
|
+
backgroundColor={Color?.gray[50]}
|
|
42
|
+
borderColor={Color?.gray[200]}
|
|
43
|
+
>
|
|
44
|
+
<Component
|
|
45
|
+
style={[
|
|
46
|
+
styles.titleWrapper,
|
|
47
|
+
files.length > 0 ? layouting.flex.col : {},
|
|
48
|
+
files.length > 0 ? spacing.gap[6] : {},
|
|
49
|
+
]}
|
|
50
|
+
{...(files?.length > 0 && { onPress: onChooseFile })}
|
|
51
|
+
>
|
|
52
|
+
<Image
|
|
53
|
+
source={require('../../assets/input-file.png')}
|
|
54
|
+
style={files?.length === 0 ? styles.image : styles.imageSmall}
|
|
55
|
+
/>
|
|
56
|
+
<Typography
|
|
57
|
+
variant={files?.length === 0 ? 't1' : 't3'}
|
|
58
|
+
weight={files?.length === 0 ? 'semibold' : 'regular'}
|
|
59
|
+
color={Color.gray[600]}
|
|
60
|
+
>
|
|
61
|
+
{title}
|
|
62
|
+
</Typography>
|
|
63
|
+
</Component>
|
|
64
|
+
|
|
65
|
+
{files.length === 0 && (
|
|
66
|
+
<Button
|
|
67
|
+
title={textButton}
|
|
68
|
+
color="primary"
|
|
69
|
+
size="small"
|
|
70
|
+
onPress={onChooseFile}
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
73
|
+
</Card>
|
|
74
|
+
|
|
75
|
+
{files.length > 0 && (
|
|
76
|
+
<ScrollView
|
|
77
|
+
style={[layouting.flex[1]]}
|
|
78
|
+
contentContainerStyle={[spacing.gap[8], layouting.align.center]}
|
|
79
|
+
horizontal
|
|
80
|
+
>
|
|
81
|
+
{files.map((file, index) => (
|
|
82
|
+
<ItemPreview
|
|
83
|
+
variant="small"
|
|
84
|
+
key={index}
|
|
85
|
+
index={index}
|
|
86
|
+
file={file}
|
|
87
|
+
onPress={() => onPreview?.(file)}
|
|
88
|
+
onDelete={() => onDelete?.(index)}
|
|
89
|
+
onReplace={() => onReplace?.(index)}
|
|
90
|
+
/>
|
|
91
|
+
))}
|
|
92
|
+
</ScrollView>
|
|
93
|
+
)}
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const styles = StyleSheet.create({
|
|
99
|
+
card: {
|
|
100
|
+
borderStyle: 'dashed',
|
|
101
|
+
flexDirection: 'row',
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
justifyContent: 'space-between',
|
|
104
|
+
gap: 8,
|
|
105
|
+
},
|
|
106
|
+
titleWrapper: {
|
|
107
|
+
flexDirection: 'row',
|
|
108
|
+
alignItems: 'center',
|
|
109
|
+
gap: 8,
|
|
110
|
+
flexShrink: 1,
|
|
111
|
+
},
|
|
112
|
+
image: {
|
|
113
|
+
width: 48,
|
|
114
|
+
height: 48,
|
|
115
|
+
},
|
|
116
|
+
imageSmall: {
|
|
117
|
+
width: 28,
|
|
118
|
+
height: 28,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StyleSheet,
|
|
3
|
+
TouchableOpacity,
|
|
4
|
+
View,
|
|
5
|
+
type TouchableOpacityProps,
|
|
6
|
+
} from 'react-native';
|
|
7
|
+
import Icon, { type IconNameProps } from '../../../Icon';
|
|
8
|
+
import Typography from '../../../Typography/Typography';
|
|
9
|
+
import Color from '../../../Color/Color';
|
|
10
|
+
|
|
11
|
+
interface Props extends TouchableOpacityProps {
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
icon: IconNameProps;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function ItemPicker({
|
|
18
|
+
title,
|
|
19
|
+
description,
|
|
20
|
+
icon,
|
|
21
|
+
...props
|
|
22
|
+
}: Props) {
|
|
23
|
+
return (
|
|
24
|
+
<TouchableOpacity {...props}>
|
|
25
|
+
<View style={styles.containerListItem}>
|
|
26
|
+
<Icon name={icon} color={Color.primary[1000]} />
|
|
27
|
+
<View>
|
|
28
|
+
<Typography variant="t2" weight="semibold" color={Color.gray[800]}>
|
|
29
|
+
{title}
|
|
30
|
+
</Typography>
|
|
31
|
+
<Typography variant="t2" color={Color.gray[600]}>
|
|
32
|
+
{description}
|
|
33
|
+
</Typography>
|
|
34
|
+
</View>
|
|
35
|
+
</View>
|
|
36
|
+
</TouchableOpacity>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const styles = StyleSheet.create({
|
|
41
|
+
containerListItem: {
|
|
42
|
+
flexDirection: 'row',
|
|
43
|
+
gap: 8,
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
|
2
|
+
import Card from '../../../Ui/Card';
|
|
3
|
+
import Color from '../../../Color/Color';
|
|
4
|
+
import Typography from '../../../Typography/Typography';
|
|
5
|
+
import Icon from '../../../Icon';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
file: any;
|
|
9
|
+
onPress: () => void;
|
|
10
|
+
index: number;
|
|
11
|
+
onReplace: (index: number) => void;
|
|
12
|
+
onDelete: (index: number) => void;
|
|
13
|
+
variant?: 'default' | 'small';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function ItemPreview({
|
|
17
|
+
file,
|
|
18
|
+
onPress,
|
|
19
|
+
index,
|
|
20
|
+
onDelete,
|
|
21
|
+
onReplace,
|
|
22
|
+
variant = 'default',
|
|
23
|
+
}: Props) {
|
|
24
|
+
const Wrapper = variant === 'default' ? Card : View;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Wrapper>
|
|
28
|
+
<TouchableOpacity
|
|
29
|
+
onPress={onPress}
|
|
30
|
+
style={styles.containerPreview}
|
|
31
|
+
activeOpacity={0.7}
|
|
32
|
+
>
|
|
33
|
+
<View style={styles.containerListItem}>
|
|
34
|
+
{file.type.startsWith('image') && (
|
|
35
|
+
<View
|
|
36
|
+
style={
|
|
37
|
+
(variant === 'small' ? styles.previewImageSmall : {},
|
|
38
|
+
variant === 'small' && file.error
|
|
39
|
+
? styles.previewImageError
|
|
40
|
+
: {})
|
|
41
|
+
}
|
|
42
|
+
>
|
|
43
|
+
{variant === 'small' && (
|
|
44
|
+
<BtnIcon onPress={() => onDelete(index)} />
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
<Image source={{ uri: file.uri }} style={styles.previewImage} />
|
|
48
|
+
</View>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{!file.type.startsWith('image') && (
|
|
52
|
+
<View
|
|
53
|
+
style={[
|
|
54
|
+
styles.previewImage,
|
|
55
|
+
variant === 'small' ? styles.previewImageSmall : {},
|
|
56
|
+
variant === 'small' && file.error
|
|
57
|
+
? styles.previewImageError
|
|
58
|
+
: {},
|
|
59
|
+
]}
|
|
60
|
+
>
|
|
61
|
+
{variant === 'small' && (
|
|
62
|
+
<BtnIcon onPress={() => onDelete(index)} />
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
<Icon
|
|
66
|
+
name={file.type === 'application/pdf' ? 'pdf-file' : 'Document'}
|
|
67
|
+
size={file.type === 'application/pdf' ? 32 : 24}
|
|
68
|
+
color={Color.primary[1000]}
|
|
69
|
+
/>
|
|
70
|
+
</View>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{variant === 'default' && (
|
|
74
|
+
<View style={styles.fileDetails}>
|
|
75
|
+
<Typography
|
|
76
|
+
variant="t1"
|
|
77
|
+
weight="semibold"
|
|
78
|
+
color={Color.gray[600]}
|
|
79
|
+
numberOfLines={1}
|
|
80
|
+
>
|
|
81
|
+
{file.name || `Image_${index + 1}`}
|
|
82
|
+
</Typography>
|
|
83
|
+
|
|
84
|
+
<Typography variant="t2" weight="regular" color={Color.gray[600]}>
|
|
85
|
+
{((file?.size ?? file?.fileSize ?? 0) / (1024 * 1024)).toFixed(
|
|
86
|
+
2
|
|
87
|
+
)}
|
|
88
|
+
MB
|
|
89
|
+
</Typography>
|
|
90
|
+
</View>
|
|
91
|
+
)}
|
|
92
|
+
</View>
|
|
93
|
+
|
|
94
|
+
{/* action default */}
|
|
95
|
+
{variant === 'default' && (
|
|
96
|
+
<View style={styles.action}>
|
|
97
|
+
{file.error && (
|
|
98
|
+
<Icon
|
|
99
|
+
name="exclamation-triangle"
|
|
100
|
+
color={Color.danger[500]}
|
|
101
|
+
size={20}
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
<TouchableOpacity onPress={() => onReplace(index)}>
|
|
106
|
+
<Icon name="rotate-right" size={18} color={Color.gray[700]} />
|
|
107
|
+
</TouchableOpacity>
|
|
108
|
+
|
|
109
|
+
<TouchableOpacity onPress={() => onDelete(index)}>
|
|
110
|
+
<Icon name="Times" size={12} color={Color.gray[700]} />
|
|
111
|
+
</TouchableOpacity>
|
|
112
|
+
</View>
|
|
113
|
+
)}
|
|
114
|
+
</TouchableOpacity>
|
|
115
|
+
</Wrapper>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const BtnIcon = ({ onPress }: { onPress: () => any }) => {
|
|
120
|
+
return (
|
|
121
|
+
<TouchableOpacity
|
|
122
|
+
style={styles.actionDelete}
|
|
123
|
+
onPress={onPress}
|
|
124
|
+
activeOpacity={0.7}
|
|
125
|
+
>
|
|
126
|
+
<Icon name="times-new" color="white" size={15} />
|
|
127
|
+
</TouchableOpacity>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const styles = StyleSheet.create({
|
|
132
|
+
containerListItem: {
|
|
133
|
+
flexDirection: 'row',
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
gap: 8,
|
|
136
|
+
flex: 1,
|
|
137
|
+
},
|
|
138
|
+
previewImage: {
|
|
139
|
+
width: 48,
|
|
140
|
+
height: 48,
|
|
141
|
+
borderRadius: 6,
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
justifyContent: 'center',
|
|
144
|
+
},
|
|
145
|
+
containerPreview: {
|
|
146
|
+
flexDirection: 'row',
|
|
147
|
+
justifyContent: 'space-between',
|
|
148
|
+
alignItems: 'center',
|
|
149
|
+
gap: 8,
|
|
150
|
+
},
|
|
151
|
+
fileDetails: {
|
|
152
|
+
justifyContent: 'center',
|
|
153
|
+
gap: 4,
|
|
154
|
+
flex: 1,
|
|
155
|
+
},
|
|
156
|
+
action: {
|
|
157
|
+
flexDirection: 'row',
|
|
158
|
+
alignItems: 'center',
|
|
159
|
+
gap: 12,
|
|
160
|
+
flexShrink: 0,
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// small
|
|
164
|
+
previewImageSmall: {
|
|
165
|
+
borderWidth: 1,
|
|
166
|
+
borderColor: Color.gray[200],
|
|
167
|
+
borderRadius: 6,
|
|
168
|
+
position: 'relative',
|
|
169
|
+
},
|
|
170
|
+
previewImageError: {
|
|
171
|
+
borderColor: Color.danger[500],
|
|
172
|
+
},
|
|
173
|
+
actionDelete: {
|
|
174
|
+
position: 'absolute',
|
|
175
|
+
top: 1.5,
|
|
176
|
+
right: 1.5,
|
|
177
|
+
backgroundColor: Color.primary[1000],
|
|
178
|
+
zIndex: 1,
|
|
179
|
+
padding: 2,
|
|
180
|
+
borderRadius: 10,
|
|
181
|
+
},
|
|
182
|
+
});
|