@finema/core 1.4.91 → 1.4.93

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 (27) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +1 -1
  3. package/dist/runtime/components/Form/Fields.vue +17 -0
  4. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +9 -17
  5. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +7 -1
  6. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/Item.vue +283 -0
  7. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +126 -0
  8. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.d.ts +22 -0
  9. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/types.mjs +0 -0
  10. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/index.vue +134 -0
  11. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/item.vue +198 -0
  12. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/types.d.ts +20 -0
  13. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/types.mjs +0 -0
  14. package/dist/runtime/components/Form/InputUploadFileClassic/index.vue +5 -3
  15. package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +9 -5
  16. package/dist/runtime/components/Form/types.d.ts +6 -2
  17. package/dist/runtime/components/Form/types.mjs +2 -0
  18. package/dist/runtime/composables/useNotification.d.ts +7 -0
  19. package/dist/runtime/composables/useNotification.mjs +39 -0
  20. package/dist/runtime/types/config.d.ts +1 -1
  21. package/dist/runtime/ui.config/index.d.ts +1 -0
  22. package/dist/runtime/ui.config/index.mjs +1 -0
  23. package/dist/runtime/ui.config/uploadDropzoneImage.d.ts +83 -0
  24. package/dist/runtime/ui.config/uploadDropzoneImage.mjs +26 -0
  25. package/dist/runtime/ui.config/uploadFileDropzone.d.ts +4 -0
  26. package/dist/runtime/ui.config/uploadFileDropzone.mjs +5 -1
  27. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "1.4.91",
3
+ "version": "1.4.93",
4
4
  "configKey": "core",
5
5
  "compatibility": {
6
6
  "nuxt": "^3.7.4"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineNuxtModule, createResolver, installModule, addPlugin, addComponentsDir, addImportsDir } from '@nuxt/kit';
2
2
 
3
3
  const name = "@finema/core";
4
- const version = "1.4.91";
4
+ const version = "1.4.93";
5
5
 
6
6
  const colors = {
7
7
  black: "#20243E",
@@ -104,6 +104,7 @@
104
104
  v-else-if="option.type === INPUT_TYPES.UPLOAD_FILE_CLASSIC_AUTO"
105
105
  :class="option.class"
106
106
  :form="form"
107
+ :request-options="option.props.requestOptions"
107
108
  v-bind="getFieldBinding(option)"
108
109
  v-on="option.on ?? {}"
109
110
  />
@@ -122,6 +123,22 @@
122
123
  v-bind="getFieldBinding(option)"
123
124
  v-on="option.on ?? {}"
124
125
  />
126
+ <FormInputUploadDropzoneAutoMultiple
127
+ v-else-if="option.type === INPUT_TYPES.UPLOAD_DROPZONE_AUTO_MULTIPLE"
128
+ :class="option.class"
129
+ :form="form"
130
+ :request-options="option.props.requestOptions"
131
+ v-bind="getFieldBinding(option)"
132
+ v-on="option.on ?? {}"
133
+ />
134
+ <FormInputUploadDropzoneImageAutoMultiple
135
+ v-else-if="option.type === INPUT_TYPES.UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE"
136
+ :class="option.class"
137
+ :form="form"
138
+ :request-options="option.props.requestOptions"
139
+ v-bind="getFieldBinding(option)"
140
+ v-on="option.on ?? {}"
141
+ />
125
142
  </template>
126
143
  </div>
127
144
  </template>
@@ -61,24 +61,12 @@
61
61
  </p>
62
62
  </div>
63
63
  <div :class="[ui.action.wrapper]">
64
- <PImage
64
+ <Icon
65
65
  v-if="isImage(selectedFile)"
66
- alt="image-popup-preview"
67
- preview
68
- :pt="{ button: 'absolute opacity-0 inset-0' }"
69
- >
70
- <template #image>
71
- <Icon :name="ui.action.previewIcon" :class="[ui.action.iconClass]" />
72
- </template>
73
- <template #preview="slotProps">
74
- <img
75
- :src="generateURL(selectedFile)"
76
- alt="preview"
77
- :style="slotProps.style"
78
- @click="slotProps.previewCallback"
79
- />
80
- </template>
81
- </PImage>
66
+ :name="ui.action.previewIcon"
67
+ :class="[ui.action.iconClass]"
68
+ @click="() => (isPreviewOpen = true)"
69
+ />
82
70
  <Icon
83
71
  :name="ui.action.downloadIcon"
84
72
  :class="[ui.action.iconClass]"
@@ -89,6 +77,9 @@
89
77
  :class="[ui.action.iconClass]"
90
78
  @click="handleDeleteFile"
91
79
  />
80
+ <Modal v-model="isPreviewOpen" :title="selectedFile?.name">
81
+ <img :src="generateURL(selectedFile)" alt="image-preview" />
82
+ </Modal>
92
83
  </div>
93
84
  </div>
94
85
  </div>
@@ -118,6 +109,7 @@ const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploa
118
109
 
119
110
  const props = withDefaults(defineProps<IUploadDropzoneProps>(), {})
120
111
  const emit = defineEmits(['change', 'delete'])
112
+ const isPreviewOpen = ref<boolean>(false)
121
113
 
122
114
  const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<File>(props)
123
115
 
@@ -192,7 +192,13 @@ import i18next from 'i18next'
192
192
 
193
193
  const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
194
194
 
195
- const props = withDefaults(defineProps<IUploadDropzoneAutoProps>(), {})
195
+ const props = withDefaults(defineProps<IUploadDropzoneAutoProps>(), {
196
+ bodyKey: 'file',
197
+ responseKey: 'url',
198
+ selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
199
+ selectFileSubLabel: 'หรือ ลากและวางที่นี่',
200
+ })
201
+
196
202
  const emits = defineEmits(['change', 'success', 'delete'])
197
203
 
198
204
  const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<File>(props)
@@ -0,0 +1,283 @@
1
+ <template>
2
+ <div
3
+ :class="[
4
+ ui.base,
5
+ ui.background.default,
6
+ {
7
+ [ui.failed]: upload.status.value.isError || errMsg,
8
+ },
9
+ ]"
10
+ >
11
+ <!-- Loading State -->
12
+ <div v-if="selectedFile && upload.status.value.isLoading" :class="[ui.onLoading.wrapper]">
13
+ <div :class="[ui.onLoading.placeholderWrapper]">
14
+ <Icon
15
+ :name="
16
+ isImage(selectedFile)
17
+ ? ui.onLoading.placeholderImgIcon || ui.default.placeholderImgIcon
18
+ : ui.onLoading.placeholderFileIcon || ui.default.filePreviewIcon
19
+ "
20
+ :class="[ui.onLoading.placeholderIconClass]"
21
+ />
22
+ </div>
23
+ <div :class="[ui.onLoading.textWrapper]">
24
+ <div class="truncate">
25
+ <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
26
+ <p class="truncate font-light text-gray-400">
27
+ {{
28
+ fileAllocate.isSelectedFileUseMb
29
+ ? `${fileAllocate.selectedFileSizeMb} MB`
30
+ : `${fileAllocate.selectedFileSizeKb} KB`
31
+ }}
32
+ - {{ percent }}% {{ uploadingLabel }}
33
+ </p>
34
+ </div>
35
+ <div>
36
+ <Icon
37
+ :name="ui.onLoading.loadingIcon || ui.default.loadingIcon"
38
+ :class="[ui.onLoading.loadingIconClass]"
39
+ />
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <!-- Success State -->
45
+ <div v-if="selectedFile && upload.status.value.isSuccess" :class="[ui.onPreview.wrapper]">
46
+ <div :class="[ui.onPreview.previewImgWrapper]">
47
+ <div v-if="isImage(selectedFile)" class="size-full overflow-hidden">
48
+ <img
49
+ :src="upload.data.value[responseKey]"
50
+ :class="[ui.onPreview.previewImgClass]"
51
+ alt="image-preview"
52
+ />
53
+ </div>
54
+ <div v-else>
55
+ <Icon
56
+ :name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
57
+ :class="[ui.onPreview.previewFileClass]"
58
+ />
59
+ </div>
60
+ </div>
61
+ <div :class="[ui.onPreview.textWrapper]">
62
+ <div class="truncate">
63
+ <h1 class="truncate font-bold" :title="selectedFile?.name">
64
+ {{ StringHelper.truncate(selectedFile?.name, 40) }}
65
+ </h1>
66
+ <p class="truncate text-sm font-light text-gray-400">
67
+ {{
68
+ fileAllocate.isSelectedFileUseMb
69
+ ? `${fileAllocate.selectedFileSizeMb} MB`
70
+ : `${fileAllocate.selectedFileSizeKb} KB`
71
+ }}
72
+ </p>
73
+ </div>
74
+ <div :class="[ui.action.wrapper]">
75
+ <Icon
76
+ v-if="isImage(selectedFile)"
77
+ :name="ui.action.previewIcon"
78
+ :class="[ui.action.iconClass]"
79
+ title="ดูตัวอย่าง"
80
+ @click="() => (isPreviewOpen = true)"
81
+ />
82
+ <Icon
83
+ :name="ui.action.downloadIcon"
84
+ :class="[ui.action.iconClass]"
85
+ title="ดาวน์โหลดไฟล์"
86
+ @click="handleDownloadFile"
87
+ />
88
+ <Icon
89
+ :name="ui.action.deleteIcon"
90
+ :class="[ui.action.iconClass]"
91
+ title="ลบไฟล์"
92
+ @click="handleDeleteFile"
93
+ />
94
+ <Modal v-model="isPreviewOpen" :title="selectedFile?.name">
95
+ <img :src="generateURL(selectedFile)" alt="image-preview" />
96
+ </Modal>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Failed State -->
102
+ <div
103
+ v-if="selectedFile && (upload.status.value.isError || errMsg)"
104
+ :class="[ui.onFailed.wrapper]"
105
+ >
106
+ <div :class="[ui.onFailed.failedImgWrapper]">
107
+ <Icon
108
+ :name="
109
+ isImage(selectedFile)
110
+ ? ui.onFailed.failedImgIcon || ui.default.placeholderImgIcon
111
+ : ui.onFailed.failedFileIcon || ui.default.filePreviewIcon
112
+ "
113
+ :class="[ui.onFailed.failedIconClass]"
114
+ />
115
+ </div>
116
+ <div :class="[ui.onFailed.textWrapper]">
117
+ <div class="truncate">
118
+ <h1 class="truncate font-bold" :title="selectedFile?.name">
119
+ {{ StringHelper.truncate(selectedFile?.name, 40) }}
120
+ </h1>
121
+ <p class="text-danger truncate font-light">
122
+ {{ errMsg ? errMsg : uploadFailedLabel }}
123
+ </p>
124
+ <Button
125
+ v-if="upload.status.value.isError"
126
+ variant="ghost"
127
+ :label="retryLabel"
128
+ :leading-icon="ui.action.retryIcon"
129
+ :class="[ui.action.retryBtnClass]"
130
+ size="sm"
131
+ @click="handleRetryUpload"
132
+ />
133
+ </div>
134
+ <div :class="[ui.action.wrapper]">
135
+ <Icon
136
+ title="ลบไฟล์"
137
+ :name="ui.action.deleteIcon"
138
+ :class="[ui.action.deleteIconClass]"
139
+ @click="handleDeleteFile"
140
+ />
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </template>
146
+ <script lang="ts" setup>
147
+ import {
148
+ checkFileType,
149
+ checkMaxSize,
150
+ downloadFileFromURL,
151
+ getFileAllocate,
152
+ generateURL,
153
+ isImage,
154
+ } from '#core/helpers/componentHelper'
155
+ import { type uploadFileDropzone } from '#core/ui.config'
156
+ import type { IUploadDropzoneAutoMultipleProps } from '#core/components/Form/InputUploadDropzoneAutoMultiple/types'
157
+ import {
158
+ computed,
159
+ type IUploadRequest,
160
+ onMounted,
161
+ ref,
162
+ StringHelper,
163
+ useUploadLoader,
164
+ useWatchTrue,
165
+ } from '#imports'
166
+ import i18next from 'i18next'
167
+
168
+ const emits = defineEmits(['success', 'error', 'delete'])
169
+ const props = defineProps<
170
+ {
171
+ ui: typeof uploadFileDropzone
172
+ selectedFile: File
173
+ } & IUploadDropzoneAutoMultipleProps
174
+ >()
175
+
176
+ const request: IUploadRequest = {
177
+ pathURL: props.uploadPathURL,
178
+ requestOptions: props.requestOptions,
179
+ }
180
+
181
+ const isPreviewOpen = ref<boolean>(false)
182
+ const percent = ref<number | string>(0)
183
+ const upload = useUploadLoader(request)
184
+ const errMsg = ref<string>('')
185
+
186
+ const acceptFile = computed(() =>
187
+ typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
188
+ )
189
+
190
+ const fileAllocate = computed(() =>
191
+ getFileAllocate(props.maxSize ?? 0, props.selectedFile?.size ?? 0)
192
+ )
193
+
194
+ const handleDeleteFile = () => {
195
+ emits('delete')
196
+ }
197
+
198
+ const handleRetryUpload = () => {
199
+ const formData = new FormData()
200
+
201
+ formData.append(props.bodyKey!, props.selectedFile)
202
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
203
+ }
204
+
205
+ const handleDownloadFile = () => {
206
+ downloadFileFromURL(URL.createObjectURL(props.selectedFile), props.selectedFile.name)
207
+ }
208
+
209
+ const onUploadProgress = (progressEvent: ProgressEvent) => {
210
+ percent.value = StringHelper.withFixed(
211
+ (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
212
+ )
213
+ }
214
+
215
+ const onDownloadProgress = (progressEvent: ProgressEvent) => {
216
+ if (progressEvent.total === 0) {
217
+ percent.value = 100
218
+
219
+ return
220
+ }
221
+
222
+ percent.value = StringHelper.withFixed(
223
+ (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
224
+ )
225
+ }
226
+
227
+ const handleCheckFileCondition = (file: File | undefined): boolean => {
228
+ if (!file) return false
229
+
230
+ const fileType = checkFileType(file, acceptFile.value ?? '')
231
+
232
+ if (!fileType) {
233
+ errMsg.value = i18next.t('custom:invalid_file_type')
234
+
235
+ return false
236
+ }
237
+
238
+ const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
239
+
240
+ if (!maxSize) {
241
+ if (fileAllocate.value.isAcceptFileUseMb) {
242
+ errMsg.value = i18next.t('custom:invalid_file_size_mb', {
243
+ size: fileAllocate.value.acceptFileSizeMb,
244
+ })
245
+ } else {
246
+ errMsg.value = i18next.t('custom:invalid_file_size_kb', {
247
+ size: fileAllocate.value.acceptFileSizeKb,
248
+ })
249
+ }
250
+
251
+ return false
252
+ }
253
+
254
+ errMsg.value = ''
255
+
256
+ return true
257
+ }
258
+
259
+ onMounted(() => {
260
+ const result = handleCheckFileCondition(props.selectedFile)
261
+
262
+ if (result) {
263
+ const formData = new FormData()
264
+
265
+ formData.append(props.bodyKey!, props.selectedFile)
266
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
267
+ }
268
+ })
269
+
270
+ useWatchTrue(
271
+ () => upload.status.value.isSuccess,
272
+ () => {
273
+ emits('success', upload.data.value)
274
+ }
275
+ )
276
+
277
+ useWatchTrue(
278
+ () => upload.status.value.isError,
279
+ () => {
280
+ emits('error', upload.status.value.errorData)
281
+ }
282
+ )
283
+ </script>
@@ -0,0 +1,126 @@
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div class="space-y-3">
4
+ <div
5
+ ref="dropzoneRef"
6
+ :class="[
7
+ ui.base,
8
+ {
9
+ [ui.disabled]: isDisabled,
10
+ [ui.background.default]: !isOverDropZone && !isDisabled,
11
+ [ui.background.dragover]: isOverDropZone && !isDisabled,
12
+ },
13
+ ]"
14
+ >
15
+ <input
16
+ ref="fileInputRef"
17
+ type="file"
18
+ class="hidden"
19
+ :name="name"
20
+ :accept="acceptFile"
21
+ :disabled="isDisabled"
22
+ multiple
23
+ @change="handleChange"
24
+ />
25
+ <div :class="[ui.wrapper]">
26
+ <div :class="[ui.placeholderWrapper]">
27
+ <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
28
+ <div :class="[ui.labelWrapper]">
29
+ <p class="text-primary cursor-pointer" @click="handleOpenFile">
30
+ {{ selectFileLabel }}
31
+ </p>
32
+ <p>{{ selectFileSubLabel }}</p>
33
+ </div>
34
+ <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <Item
39
+ v-for="(file, index) in selectedFiles"
40
+ :key="file.name + index"
41
+ v-bind="$props"
42
+ :ui="ui"
43
+ :selected-file="file"
44
+ @success="handleSuccess(index, $event)"
45
+ @delete="handleDeleteFile(index)"
46
+ @error="handleError(index, $event)"
47
+ />
48
+ </div>
49
+ </FieldWrapper>
50
+ </template>
51
+
52
+ <script lang="ts" setup>
53
+ import { useDropZone } from '@vueuse/core'
54
+ import { type IUploadDropzoneAutoMultipleProps } from './types'
55
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
56
+ import { useFieldHOC } from '#core/composables/useForm'
57
+ import { _get, computed, ref, toRef, useUI, useUiConfig } from '#imports'
58
+ import { uploadFileDropzone } from '#core/ui.config'
59
+ import Item from './Item.vue'
60
+
61
+ const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
62
+
63
+ const props = withDefaults(defineProps<IUploadDropzoneAutoMultipleProps>(), {
64
+ bodyKey: 'file',
65
+ responseKey: 'url',
66
+ selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
67
+ selectFileSubLabel: 'หรือ ลากและวางที่นี่',
68
+ retryLabel: 'ลองอีกครั้ง',
69
+ uploadingLabel: 'กำลังอัพโหลด...',
70
+ uploadFailedLabel: 'อัพโหลดล้มเหลว, กรุณาลองอีกครั้ง',
71
+ })
72
+
73
+ const emits = defineEmits(['change', 'success', 'delete'])
74
+ const selectedFiles = ref<File[]>([])
75
+
76
+ const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<File[]>(props)
77
+
78
+ const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
79
+
80
+ const fileInputRef = ref<HTMLInputElement>()
81
+ const dropzoneRef = ref<HTMLDivElement>()
82
+
83
+ const acceptFile = computed(() =>
84
+ typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
85
+ )
86
+
87
+ const onDrop = (files: File[] | null) => {
88
+ if (props.isDisabled || files?.length === 0 || !files) return
89
+
90
+ for (const file of files) {
91
+ selectedFiles.value = [file, ...selectedFiles.value]
92
+ emits('change', value.value)
93
+ }
94
+ }
95
+
96
+ const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
97
+ onDrop,
98
+ })
99
+
100
+ const handleChange = (e: Event) => {
101
+ if (props.isDisabled) return
102
+
103
+ for (const file of (e.target as HTMLInputElement).files ?? []) {
104
+ selectedFiles.value = [file, ...selectedFiles.value]
105
+ emits('change', value.value)
106
+ }
107
+ }
108
+
109
+ const handleOpenFile = () => {
110
+ fileInputRef.value?.click()
111
+ }
112
+
113
+ const handleDeleteFile = (index: number) => {
114
+ selectedFiles.value.splice(index, 1)
115
+ onChange(undefined)
116
+ emits('delete')
117
+ }
118
+
119
+ const handleError = (index: number, error: any) => {}
120
+
121
+ const handleSuccess = (index: number, any: any) => {
122
+ value.value = [_get(any, props.responseKey), ...(value.value || [])]
123
+ emits('change', value.value)
124
+ emits('success', value.value)
125
+ }
126
+ </script>
@@ -0,0 +1,22 @@
1
+ import { type AxiosRequestConfig } from 'axios';
2
+ import { type IFieldProps, type IFormFieldBase, type INPUT_TYPES } from '../types';
3
+ export interface IUploadDropzoneAutoMultipleProps extends IFieldProps {
4
+ requestOptions: Omit<AxiosRequestConfig, 'baseURL'> & {
5
+ baseURL: string;
6
+ };
7
+ uploadPathURL?: string;
8
+ selectFileLabel?: string;
9
+ selectFileSubLabel?: string;
10
+ uploadingLabel?: string;
11
+ uploadFailedLabel?: string;
12
+ retryLabel?: string;
13
+ accept?: string[] | string;
14
+ bodyKey?: string;
15
+ responseKey?: string;
16
+ maxSize?: number;
17
+ }
18
+ export type IUploadDropzoneAutoMultipleField = IFormFieldBase<INPUT_TYPES.UPLOAD_DROPZONE_AUTO_MULTIPLE, IUploadDropzoneAutoMultipleProps, {
19
+ change: (value: File[] | undefined) => void;
20
+ success: (res: any) => void;
21
+ delete: () => void;
22
+ }>;
@@ -0,0 +1,134 @@
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div class="space-y-3">
4
+ <div
5
+ ref="dropzoneRef"
6
+ :class="[
7
+ ui.base,
8
+ {
9
+ [ui.disabled]: isDisabled,
10
+ [ui.background.default]: !isOverDropZone && !isDisabled,
11
+ [ui.background.dragover]: isOverDropZone && !isDisabled,
12
+ },
13
+ ]"
14
+ >
15
+ <input
16
+ ref="fileInputRef"
17
+ type="file"
18
+ class="hidden"
19
+ :name="name"
20
+ :accept="acceptFile"
21
+ :disabled="isDisabled"
22
+ multiple
23
+ @change="handleChange"
24
+ />
25
+ <div :class="[ui.wrapper]">
26
+ <div v-if="selectedFiles.length === 0" :class="[ui.placeholderWrapper]">
27
+ <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
28
+ <div :class="[ui.labelWrapper]">
29
+ <p class="text-primary cursor-pointer" @click="handleOpenFile">
30
+ {{ selectFileLabel }}
31
+ </p>
32
+ <p>{{ selectFileSubLabel }}</p>
33
+ </div>
34
+ <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
35
+ </div>
36
+
37
+ <div v-else :class="ui.imageItemWrapper">
38
+ <Item
39
+ v-for="(file, index) in selectedFiles"
40
+ :key="file.name + index"
41
+ v-bind="$props"
42
+ :ui="ui"
43
+ :selected-file="file"
44
+ @success="handleSuccess(index, $event)"
45
+ @delete="handleDeleteFile(index)"
46
+ @error="handleError(index, $event)"
47
+ />
48
+
49
+ <Item v-bind="$props" is-adding-btn :ui="ui" @add="handleOpenFile" />
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </FieldWrapper>
55
+ </template>
56
+
57
+ <script lang="ts" setup>
58
+ import { useDropZone } from '@vueuse/core'
59
+ import { type IUploadDropzoneImageAutoMultipleProps } from './types'
60
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
61
+ import { useFieldHOC } from '#core/composables/useForm'
62
+ import { _get, computed, ref, toRef, useUI, useUiConfig } from '#imports'
63
+ import { uploadFileDropzoneImage } from '#core/ui.config'
64
+ import Item from './item.vue'
65
+
66
+ const config = useUiConfig<typeof uploadFileDropzoneImage>(
67
+ uploadFileDropzoneImage,
68
+ 'uploadFileDropzoneImage'
69
+ )
70
+
71
+ const props = withDefaults(defineProps<IUploadDropzoneImageAutoMultipleProps>(), {
72
+ accept: 'image/*',
73
+ bodyKey: 'file',
74
+ responseKey: 'url',
75
+ selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
76
+ selectFileSubLabel: 'หรือ ลากและวางที่นี่',
77
+ uploadAddLabel: 'อัพโหลด',
78
+ })
79
+
80
+ const emits = defineEmits(['change', 'success', 'delete'])
81
+ const selectedFiles = ref<File[]>([])
82
+
83
+ const { wrapperProps, handleChange: onChange, value } = useFieldHOC<File[]>(props)
84
+
85
+ const { ui } = useUI('uploadFileDropzoneImage', toRef(props, 'ui'), config)
86
+
87
+ const fileInputRef = ref<HTMLInputElement>()
88
+ const dropzoneRef = ref<HTMLDivElement>()
89
+
90
+ const acceptFile = computed(() =>
91
+ typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
92
+ )
93
+
94
+ const onDrop = (files: File[] | null) => {
95
+ if (props.isDisabled || files?.length === 0 || !files) return
96
+
97
+ for (const file of files) {
98
+ selectedFiles.value = [...selectedFiles.value, file]
99
+
100
+ emits('change', value.value)
101
+ }
102
+ }
103
+
104
+ const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
105
+ onDrop,
106
+ })
107
+
108
+ const handleChange = (e: Event) => {
109
+ if (props.isDisabled) return
110
+
111
+ for (const file of (e.target as HTMLInputElement).files ?? []) {
112
+ selectedFiles.value = [...selectedFiles.value, file]
113
+ emits('change', value.value)
114
+ }
115
+ }
116
+
117
+ const handleOpenFile = () => {
118
+ fileInputRef.value?.click()
119
+ }
120
+
121
+ const handleDeleteFile = (index: number) => {
122
+ selectedFiles.value.splice(index, 1)
123
+ onChange(undefined)
124
+ emits('delete')
125
+ }
126
+
127
+ const handleError = (index: number, error: any) => {}
128
+
129
+ const handleSuccess = (index: number, any: any) => {
130
+ value.value = [_get(any, props.responseKey), ...(value.value || [])]
131
+ emits('change', value.value)
132
+ emits('success', value.value)
133
+ }
134
+ </script>
@@ -0,0 +1,198 @@
1
+ <template>
2
+ <div :class="[ui.imageItem.wrapper]">
3
+ <div v-if="isAddingBtn" class="w-full">
4
+ <div :class="[ui.action.addingWrapper]" @click="$emit('add')">
5
+ <Icon :name="ui.action.addingIcon" :class="[ui.action.addingBtnClass]" />
6
+ <p :class="[ui.action.addingTextClass]">{{ uploadAddLabel }}</p>
7
+ </div>
8
+ </div>
9
+
10
+ <!-- Loading State -->
11
+ <div v-if="selectedFile && upload.status.value.isLoading" class="w-full">
12
+ <div :class="[ui.imageItem.onLoading.wrapper]">
13
+ <div :class="[ui.imageItem.onLoading.percentClass]">{{ percent }}%</div>
14
+ <UProgress :value="percent" />
15
+ </div>
16
+ </div>
17
+
18
+ <!-- Success State -->
19
+ <div v-if="selectedFile && upload.status.value.isSuccess" class="w-full">
20
+ <div :class="[ui.imageItem.onPreview.wrapper]">
21
+ <img
22
+ :class="[ui.imageItem.onPreview.previewImgClass]"
23
+ :src="upload.data.value[responseKey]"
24
+ alt="img"
25
+ />
26
+ <div :class="[ui.imageItem.onPreview.previewActionWrapper]">
27
+ <Icon
28
+ title="ดูตัวอย่าง"
29
+ :name="ui.action.previewIcon"
30
+ :class="[ui.imageItem.onPreview.actionBtnClass]"
31
+ @click="() => (isPreviewOpen = true)"
32
+ />
33
+ <Icon
34
+ title="ลบไฟล์"
35
+ :name="ui.action.deleteIcon"
36
+ :class="[ui.imageItem.onPreview.actionBtnClass]"
37
+ @click="handleDeleteFile"
38
+ />
39
+ <Modal v-model="isPreviewOpen" :title="selectedFile?.name">
40
+ <img :src="upload.data.value[responseKey]" alt="image-preview" />
41
+ </Modal>
42
+ </div>
43
+ </div>
44
+
45
+ <div :class="[ui.imageItem.onPreview.previewTextWrapper]">
46
+ <p :class="[ui.imageItem.onPreview.previewText]">{{ selectedFile.name }}</p>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Failed State -->
51
+ <div v-if="selectedFile && upload.status.value.isError" class="w-full">
52
+ <div :class="[ui.imageItem.onFailed.wrapper]">
53
+ <img
54
+ :class="[ui.imageItem.onFailed.failedImgClass]"
55
+ :src="generateURL(selectedFile)"
56
+ alt="img"
57
+ />
58
+ <div :class="[ui.imageItem.onFailed.failedActionWrapper]">
59
+ <Icon
60
+ title="ลบไฟล์"
61
+ :name="ui.action.deleteIcon"
62
+ :class="[ui.imageItem.onFailed.actionBtnClass]"
63
+ @click="handleDeleteFile"
64
+ />
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </template>
70
+
71
+ <script lang="ts" setup>
72
+ import {
73
+ checkFileType,
74
+ checkMaxSize,
75
+ generateURL,
76
+ getFileAllocate,
77
+ } from '#core/helpers/componentHelper'
78
+ import { type uploadFileDropzoneImage } from '#core/ui.config'
79
+ import type { IUploadDropzoneImageAutoMultipleProps } from '#core/components/Form/InputUploadDropzoneImageAutoMultiple/types'
80
+ import {
81
+ computed,
82
+ type IUploadRequest,
83
+ onMounted,
84
+ ref,
85
+ StringHelper,
86
+ useUploadLoader,
87
+ useWatchTrue,
88
+ } from '#imports'
89
+ import i18next from 'i18next'
90
+
91
+ const emits = defineEmits(['success', 'error', 'delete', 'add'])
92
+ const props = defineProps<
93
+ {
94
+ ui: typeof uploadFileDropzoneImage
95
+ selectedFile?: File
96
+ isAddingBtn?: boolean
97
+ } & IUploadDropzoneImageAutoMultipleProps
98
+ >()
99
+
100
+ const request: IUploadRequest = {
101
+ pathURL: props.uploadPathURL,
102
+ requestOptions: props.requestOptions,
103
+ }
104
+
105
+ const isPreviewOpen = ref<boolean>(false)
106
+ const percent = ref<number | string>(0)
107
+ const upload = useUploadLoader(request)
108
+ const errMsg = ref<string>('')
109
+
110
+ const acceptFile = computed(() =>
111
+ typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
112
+ )
113
+
114
+ const fileAllocate = computed(() =>
115
+ getFileAllocate(props.maxSize ?? 0, props.selectedFile?.size ?? 0)
116
+ )
117
+
118
+ const handleDeleteFile = () => {
119
+ emits('delete')
120
+ }
121
+
122
+ const onUploadProgress = (progressEvent: ProgressEvent) => {
123
+ percent.value = StringHelper.withFixed(
124
+ (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
125
+ )
126
+ }
127
+
128
+ const onDownloadProgress = (progressEvent: ProgressEvent) => {
129
+ if (progressEvent.total === 0) {
130
+ percent.value = 100
131
+
132
+ return
133
+ }
134
+
135
+ percent.value = StringHelper.withFixed(
136
+ (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
137
+ )
138
+ }
139
+
140
+ const handleCheckFileCondition = (file: File | undefined): boolean => {
141
+ if (!file) return false
142
+
143
+ const fileType = checkFileType(file, acceptFile.value ?? '')
144
+
145
+ if (!fileType) {
146
+ errMsg.value = i18next.t('custom:invalid_file_type')
147
+
148
+ return false
149
+ }
150
+
151
+ const maxSize = checkMaxSize(file, fileAllocate.value.acceptFileSizeKb ?? 0)
152
+
153
+ if (!maxSize) {
154
+ if (fileAllocate.value.isAcceptFileUseMb) {
155
+ errMsg.value = i18next.t('custom:invalid_file_size_mb', {
156
+ size: fileAllocate.value.acceptFileSizeMb,
157
+ })
158
+ } else {
159
+ errMsg.value = i18next.t('custom:invalid_file_size_kb', {
160
+ size: fileAllocate.value.acceptFileSizeKb,
161
+ })
162
+ }
163
+
164
+ return false
165
+ }
166
+
167
+ errMsg.value = ''
168
+
169
+ return true
170
+ }
171
+
172
+ onMounted(() => {
173
+ if (props.isAddingBtn) return
174
+
175
+ const result = handleCheckFileCondition(props.selectedFile)
176
+
177
+ if (result) {
178
+ const formData = new FormData()
179
+
180
+ formData.append(props.bodyKey!, props.selectedFile!)
181
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
182
+ }
183
+ })
184
+
185
+ useWatchTrue(
186
+ () => upload.status.value.isSuccess,
187
+ () => {
188
+ emits('success', upload.data.value)
189
+ }
190
+ )
191
+
192
+ useWatchTrue(
193
+ () => upload.status.value.isError,
194
+ () => {
195
+ emits('error', upload.status.value.errorData)
196
+ }
197
+ )
198
+ </script>
@@ -0,0 +1,20 @@
1
+ import { type AxiosRequestConfig } from 'axios';
2
+ import { type IFieldProps, type IFormFieldBase, type INPUT_TYPES } from '../types';
3
+ export interface IUploadDropzoneImageAutoMultipleProps extends IFieldProps {
4
+ requestOptions: Omit<AxiosRequestConfig, 'baseURL'> & {
5
+ baseURL: string;
6
+ };
7
+ uploadPathURL?: string;
8
+ selectFileLabel?: string;
9
+ selectFileSubLabel?: string;
10
+ uploadAddLabel?: string;
11
+ accept?: string[] | string;
12
+ bodyKey?: string;
13
+ responseKey?: string;
14
+ maxSize?: number;
15
+ }
16
+ export type IUploadDropzoneImageAutoMultipleField = IFormFieldBase<INPUT_TYPES.UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE, IUploadDropzoneImageAutoMultipleProps, {
17
+ change: (value: File[] | undefined) => void;
18
+ success: (res: any) => void;
19
+ delete: () => void;
20
+ }>;
@@ -12,9 +12,9 @@
12
12
  />
13
13
  <div :class="[ui.wrapper]">
14
14
  <div :class="[ui.selectFileBox]">
15
- <Button size="2xs" @click="handleOpenFile">{{ selectFileLabel || 'Choose File' }}</Button>
15
+ <Button size="2xs" @click="handleOpenFile">{{ selectFileLabel }}</Button>
16
16
  <p :class="ui.placeholder">
17
- {{ value?.name ?? placeholder ?? 'No file chosen' }}
17
+ {{ value?.name ?? placeholder ?? 'ยังไม่ได้เลือกไฟล์' }}
18
18
  </p>
19
19
  <Badge v-if="value" size="xs" variant="outline">
20
20
  {{ isSelectedFileUseMb ? `${selectedFileSizeMb} MB` : `${selectedFileSizeKb} KB` }}
@@ -39,7 +39,9 @@ const config = useUiConfig<typeof uploadFileInputClassicAuto>(
39
39
  )
40
40
 
41
41
  const emits = defineEmits(['change'])
42
- const props = withDefaults(defineProps<IUploadFileClassicFieldProps>(), {})
42
+ const props = withDefaults(defineProps<IUploadFileClassicFieldProps>(), {
43
+ selectFileLabel: 'เลือกไฟล์',
44
+ })
43
45
 
44
46
  const { value, wrapperProps, setErrors } = useFieldHOC<File | undefined>(props)
45
47
  const selectedFileSizeKb = computed(() => ((value.value?.size || 0) / 1000).toFixed(2))
@@ -12,9 +12,9 @@
12
12
  />
13
13
  <div :class="[ui.wrapper]">
14
14
  <div :class="[ui.selectFileBox]">
15
- <Button size="2xs" @click="handleOpenFile">{{ selectFileLabel || 'Choose File' }}</Button>
15
+ <Button size="2xs" @click="handleOpenFile">{{ selectFileLabel }}</Button>
16
16
  <p :class="ui.placeholder">
17
- {{ selectedFile?.name ?? placeholder ?? 'No file chosen' }}
17
+ {{ selectedFile?.name ?? placeholder ?? 'ยังไม่ได้เลือกไฟล์' }}
18
18
  </p>
19
19
  <Badge v-if="selectedFile" size="xs" variant="outline">
20
20
  {{ isSelectedFileUseMb ? `${selectedFileSizeMb} MB` : `${selectedFileSizeKb} KB` }}
@@ -67,7 +67,11 @@ const config = useUiConfig<typeof uploadFileInputClassicAuto>(
67
67
  )
68
68
 
69
69
  const emits = defineEmits(['success'])
70
- const props = withDefaults(defineProps<IUploadFileProps>(), {})
70
+ const props = withDefaults(defineProps<IUploadFileProps>(), {
71
+ bodyKey: 'file',
72
+ responseKey: 'url',
73
+ selectFileLabel: 'เลือกไฟล์',
74
+ })
71
75
 
72
76
  const { wrapperProps, setErrors, value } = useFieldHOC<string>(props)
73
77
 
@@ -110,7 +114,7 @@ const handleChange = (e: Event) => {
110
114
  selectedFile.value = file
111
115
  const formData = new FormData()
112
116
 
113
- formData.append(props.bodyKey || 'file', file)
117
+ formData.append(props.bodyKey, file)
114
118
  upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
115
119
  }
116
120
  }
@@ -160,7 +164,7 @@ const onDownloadProgress = (progressEvent: ProgressEvent) => {
160
164
  useWatchTrue(
161
165
  () => upload.status.value.isSuccess,
162
166
  () => {
163
- value.value = _get(upload.data.value, props.responseKey || 'url')
167
+ value.value = _get(upload.data.value, props.responseKey)
164
168
  emits('success', upload.data.value)
165
169
  }
166
170
  )
@@ -13,6 +13,8 @@ import { type IUploadFileField } from '#core/components/Form/InputUploadFileClas
13
13
  import { type IUploadDropzoneField } from '#core/components/Form/InputUploadDropzone/types';
14
14
  import { type IUploadDropzoneAutoField } from '#core/components/Form/InputUploadDropzoneAuto/types';
15
15
  import type { INumberField } from '#core/components/Form/InputNumber/types';
16
+ import type { IUploadDropzoneAutoMultipleField } from '#core/components/Form/InputUploadDropzoneAutoMultiple/types';
17
+ import type { IUploadDropzoneImageAutoMultipleField } from '#core/components/Form/InputUploadDropzoneImageAutoMultiple/types';
16
18
  export declare const enum INPUT_TYPES {
17
19
  TEXT = "TEXT",
18
20
  NUMBER = "NUMBER",
@@ -29,7 +31,9 @@ export declare const enum INPUT_TYPES {
29
31
  UPLOAD_FILE_CLASSIC = "UPLOAD_FILE_CLASSIC",
30
32
  UPLOAD_FILE_CLASSIC_AUTO = "UPLOAD_FILE_CLASSIC_AUTO",
31
33
  UPLOAD_DROPZONE = "UPLOAD_DROPZONE",
32
- UPLOAD_DROPZONE_AUTO = "UPLOAD_DROPZONE_AUTO"
34
+ UPLOAD_DROPZONE_AUTO = "UPLOAD_DROPZONE_AUTO",
35
+ UPLOAD_DROPZONE_AUTO_MULTIPLE = "UPLOAD_DROPZONE_AUTO_MULTIPLE",
36
+ UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE = "UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE"
33
37
  }
34
38
  export interface IFieldProps {
35
39
  form?: FormContext;
@@ -58,4 +62,4 @@ export interface IFormFieldBase<I extends INPUT_TYPES, P extends IFieldProps, O>
58
62
  props: P;
59
63
  on?: O;
60
64
  }
61
- export type IFormField = ITextField | INumberField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IUploadFileClassicField | IUploadFileField | IUploadDropzoneField | IUploadDropzoneAutoField;
65
+ export type IFormField = ITextField | INumberField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IUploadFileClassicField | IUploadFileField | IUploadDropzoneField | IUploadDropzoneAutoField | IUploadDropzoneAutoMultipleField | IUploadDropzoneImageAutoMultipleField;
@@ -15,5 +15,7 @@ export var INPUT_TYPES = /* @__PURE__ */ ((INPUT_TYPES2) => {
15
15
  INPUT_TYPES2["UPLOAD_FILE_CLASSIC_AUTO"] = "UPLOAD_FILE_CLASSIC_AUTO";
16
16
  INPUT_TYPES2["UPLOAD_DROPZONE"] = "UPLOAD_DROPZONE";
17
17
  INPUT_TYPES2["UPLOAD_DROPZONE_AUTO"] = "UPLOAD_DROPZONE_AUTO";
18
+ INPUT_TYPES2["UPLOAD_DROPZONE_AUTO_MULTIPLE"] = "UPLOAD_DROPZONE_AUTO_MULTIPLE";
19
+ INPUT_TYPES2["UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE"] = "UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE";
18
20
  return INPUT_TYPES2;
19
21
  })(INPUT_TYPES || {});
@@ -0,0 +1,7 @@
1
+ export declare const useNotification: () => {
2
+ info: (notification: Notification) => void;
3
+ success: (notification: Notification) => void;
4
+ warning: (notification: Notification) => void;
5
+ error: (notification: Notification) => void;
6
+ remove: any;
7
+ };
@@ -0,0 +1,39 @@
1
+ import { useToast } from "#imports";
2
+ export const useNotification = () => {
3
+ const toast = useToast();
4
+ const info = (notification) => {
5
+ toast.add({
6
+ icon: "ph:info",
7
+ color: "info",
8
+ ...notification
9
+ });
10
+ };
11
+ const success = (notification) => {
12
+ toast.add({
13
+ icon: "ph:check-circle",
14
+ color: "success",
15
+ ...notification
16
+ });
17
+ };
18
+ const warning = (notification) => {
19
+ toast.add({
20
+ icon: "ph:warning",
21
+ color: "warning",
22
+ ...notification
23
+ });
24
+ };
25
+ const error = (notification) => {
26
+ toast.add({
27
+ icon: "ph:x-circle",
28
+ color: "danger",
29
+ ...notification
30
+ });
31
+ };
32
+ return {
33
+ info,
34
+ success,
35
+ warning,
36
+ error,
37
+ remove: toast.remove
38
+ };
39
+ };
@@ -1 +1 @@
1
- export type UIComponentList = 'modal' | 'slideover' | 'dropdown' | 'icon' | 'button' | 'buttonGroup' | 'tabs' | 'card' | 'breadcrumb' | 'badge' | 'input' | 'pagination' | 'notification' | 'uploadFileInputClassicAuto' | 'uploadFileDropzone';
1
+ export type UIComponentList = 'modal' | 'slideover' | 'dropdown' | 'icon' | 'button' | 'buttonGroup' | 'tabs' | 'card' | 'breadcrumb' | 'badge' | 'input' | 'pagination' | 'notification' | 'uploadFileInputClassicAuto' | 'uploadFileDropzone' | 'uploadFileDropzoneImage';
@@ -11,3 +11,4 @@ export { slideover } from './slideover';
11
11
  export { breadcrumb } from './breadcrumb';
12
12
  export { uploadFileInputClassicAuto } from './uploadFileInputClassicAuto';
13
13
  export { uploadFileDropzone } from './uploadFileDropzone';
14
+ export { uploadFileDropzoneImage } from './uploadDropzoneImage';
@@ -11,3 +11,4 @@ export { slideover } from "./slideover.mjs";
11
11
  export { breadcrumb } from "./breadcrumb.mjs";
12
12
  export { uploadFileInputClassicAuto } from "./uploadFileInputClassicAuto.mjs";
13
13
  export { uploadFileDropzone } from "./uploadFileDropzone.mjs";
14
+ export { uploadFileDropzoneImage } from "./uploadDropzoneImage.mjs";
@@ -0,0 +1,83 @@
1
+ export declare const uploadFileDropzoneImage: {
2
+ imageItemWrapper: string;
3
+ imageItem: {
4
+ wrapper: string;
5
+ onLoading: {
6
+ wrapper: string;
7
+ percentClass: string;
8
+ };
9
+ onPreview: {
10
+ wrapper: string;
11
+ previewImgClass: string;
12
+ previewActionWrapper: string;
13
+ actionBtnClass: string;
14
+ previewTextWrapper: string;
15
+ previewText: string;
16
+ };
17
+ onFailed: {
18
+ wrapper: string;
19
+ failedImgClass: string;
20
+ failedActionWrapper: string;
21
+ actionBtnClass: string;
22
+ };
23
+ };
24
+ base: string;
25
+ wrapper: string;
26
+ disabled: string;
27
+ failed: string;
28
+ placeholderWrapper: string;
29
+ placeholder: string;
30
+ labelWrapper: string;
31
+ onLoading: {
32
+ wrapper: string;
33
+ placeholderWrapper: string;
34
+ placeholderImgIcon: string;
35
+ placeholderFileIcon: string;
36
+ placeholderIconClass: string;
37
+ textWrapper: string;
38
+ loadingIcon: string;
39
+ loadingIconClass: string;
40
+ };
41
+ onPreview: {
42
+ wrapper: string;
43
+ previewImgWrapper: string;
44
+ previewImgClass: string;
45
+ previewFileIcon: string;
46
+ previewFileClass: string;
47
+ textWrapper: string;
48
+ };
49
+ onFailed: {
50
+ wrapper: string;
51
+ failedImgWrapper: string;
52
+ failedImgIcon: string;
53
+ failedFileIcon: string;
54
+ failedIconClass: string;
55
+ textWrapper: string;
56
+ };
57
+ action: {
58
+ wrapper: string;
59
+ addingWrapper: string;
60
+ iconClass: string;
61
+ deleteIconClass: string;
62
+ retryBtnClass: string;
63
+ addingBtnClass: string;
64
+ addingTextClass: string;
65
+ previewIcon: string;
66
+ downloadIcon: string;
67
+ deleteIcon: string;
68
+ retryIcon: string;
69
+ addingIcon: string;
70
+ };
71
+ background: {
72
+ default: string;
73
+ dragover: string;
74
+ };
75
+ labelIcon: string;
76
+ default: {
77
+ filePreviewIcon: string;
78
+ uploadIcon: string;
79
+ placeholderImgIcon: string;
80
+ failedImgIcon: string;
81
+ loadingIcon: string;
82
+ };
83
+ };
@@ -0,0 +1,26 @@
1
+ import { uploadFileDropzone } from "./uploadFileDropzone.mjs";
2
+ export const uploadFileDropzoneImage = {
3
+ ...uploadFileDropzone,
4
+ imageItemWrapper: "flex w-full flex-wrap gap-4",
5
+ imageItem: {
6
+ wrapper: "max-w-[96px]",
7
+ onLoading: {
8
+ wrapper: "flex size-24 flex-col items-center justify-center overflow-hidden rounded-lg border-[1px] border-dashed p-2",
9
+ percentClass: "text-primary"
10
+ },
11
+ onPreview: {
12
+ wrapper: "relative size-24 overflow-hidden rounded-lg ring-[1px] ring-gray-100",
13
+ previewImgClass: "size-full object-cover",
14
+ previewActionWrapper: "absolute inset-0 z-10 flex items-center justify-center space-x-2 opacity-0 transition hover:bg-black/50 hover:opacity-100",
15
+ actionBtnClass: "size-7 cursor-pointer text-white",
16
+ previewTextWrapper: "mt-2 truncate",
17
+ previewText: "truncate text-center text-sm"
18
+ },
19
+ onFailed: {
20
+ wrapper: "ring-danger relative size-24 overflow-hidden rounded-lg ring-[1px]",
21
+ failedImgClass: "size-full object-cover",
22
+ failedActionWrapper: "absolute inset-0 z-10 flex items-center justify-center space-x-2 bg-white/50",
23
+ actionBtnClass: "text-danger size-7 cursor-pointer"
24
+ }
25
+ }
26
+ };
@@ -34,13 +34,17 @@ export declare const uploadFileDropzone: {
34
34
  };
35
35
  action: {
36
36
  wrapper: string;
37
+ addingWrapper: string;
37
38
  iconClass: string;
38
39
  deleteIconClass: string;
39
40
  retryBtnClass: string;
41
+ addingBtnClass: string;
42
+ addingTextClass: string;
40
43
  previewIcon: string;
41
44
  downloadIcon: string;
42
45
  deleteIcon: string;
43
46
  retryIcon: string;
47
+ addingIcon: string;
44
48
  };
45
49
  background: {
46
50
  default: string;
@@ -34,13 +34,17 @@ export const uploadFileDropzone = {
34
34
  },
35
35
  action: {
36
36
  wrapper: "flex items-center space-x-4",
37
+ addingWrapper: "flex size-24 flex-col items-center justify-center overflow-hidden rounded-lg border-[1px] border-dashed p-2 cursor-pointer transition hover:bg-gray-100",
37
38
  iconClass: "size-6 text-gray-400 cursor-pointer",
38
39
  deleteIconClass: "size-6 text-danger cursor-pointer",
39
40
  retryBtnClass: "px-0",
41
+ addingBtnClass: "size-7 text-gray-500",
42
+ addingTextClass: "text-center text-sm text-gray-500",
40
43
  previewIcon: "i-ic:outline-remove-red-eye",
41
44
  downloadIcon: "i-ic:outline-file-download",
42
45
  deleteIcon: "ph:trash",
43
- retryIcon: "ph:arrow-counter-clockwise"
46
+ retryIcon: "ph:arrow-counter-clockwise",
47
+ addingIcon: "i-material-symbols:add"
44
48
  },
45
49
  background: {
46
50
  default: "bg-white border-gray-border",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "1.4.91",
3
+ "version": "1.4.93",
4
4
  "repository": "https://gitlab.finema.co/finema/ui-kit",
5
5
  "license": "MIT",
6
6
  "author": "Finema Dev Core Team",