@finema/core 2.12.2 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +1 -1
  3. package/dist/runtime/components/FlexDeck/Base.vue +1 -1
  4. package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue +35 -0
  5. package/dist/runtime/components/Form/InputUploadDropzoneAuto/EmptyState.vue.d.ts +12 -0
  6. package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue +54 -0
  7. package/dist/runtime/components/Form/InputUploadDropzoneAuto/FailedState.vue.d.ts +14 -0
  8. package/dist/runtime/components/Form/InputUploadDropzoneAuto/LoadingState.vue +54 -0
  9. package/dist/runtime/components/Form/InputUploadDropzoneAuto/LoadingState.vue.d.ts +8 -0
  10. package/dist/runtime/components/Form/InputUploadDropzoneAuto/PreviewModal.vue +35 -0
  11. package/dist/runtime/components/Form/InputUploadDropzoneAuto/PreviewModal.vue.d.ts +10 -0
  12. package/dist/runtime/components/Form/InputUploadDropzoneAuto/SuccessState.vue +88 -0
  13. package/dist/runtime/components/Form/InputUploadDropzoneAuto/SuccessState.vue.d.ts +17 -0
  14. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +91 -76
  15. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue.d.ts +10 -6
  16. package/dist/runtime/components/Form/InputUploadDropzoneAuto/types.d.ts +7 -5
  17. package/dist/runtime/components/Form/InputUploadDropzoneAuto/useUploadState.d.ts +25 -0
  18. package/dist/runtime/components/Form/InputUploadDropzoneAuto/useUploadState.js +187 -0
  19. package/dist/runtime/components/Image.vue +1 -1
  20. package/dist/runtime/components/Table/Base.vue +77 -77
  21. package/dist/runtime/components/Table/Base.vue.d.ts +5 -5
  22. package/dist/runtime/components/Table/Simple.vue +21 -21
  23. package/dist/runtime/components/Table/index.vue +4 -3
  24. package/dist/runtime/components/Table/index.vue.d.ts +1 -1
  25. package/dist/runtime/components/Table/types.d.ts +1 -2
  26. package/dist/runtime/composables/useUpload.js +2 -4
  27. package/dist/runtime/helpers/componentHelper.d.ts +3 -2
  28. package/dist/runtime/helpers/componentHelper.js +6 -2
  29. package/dist/runtime/theme/uploadFileDropzone.d.ts +32 -0
  30. package/dist/runtime/theme/uploadFileDropzone.js +41 -5
  31. package/package.json +1 -1
@@ -0,0 +1,187 @@
1
+ import { useDropZone, useFileDialog } from "@vueuse/core";
2
+ import PreviewModal from "./PreviewModal.vue";
3
+ import { computed, ref, useOverlay, watch } from "#imports";
4
+ import { useUploadLoader } from "#core/composables/useUpload";
5
+ import { useFileAllocate, useFileProgress } from "#core/helpers/componentHelper";
6
+ import { StringHelper } from "#core/utils/StringHelper";
7
+ import { _get } from "#core/utils/lodash";
8
+ const overlay = useOverlay();
9
+ const previewModal = overlay.create(PreviewModal);
10
+ export var UploadState = /* @__PURE__ */ ((UploadState2) => {
11
+ UploadState2["EMPTY"] = "empty";
12
+ UploadState2["UPLOADING"] = "uploading";
13
+ UploadState2["SUCCESS"] = "success";
14
+ UploadState2["ERROR"] = "error";
15
+ return UploadState2;
16
+ })(UploadState || {});
17
+ export const useUploadState = (props, emits, onChange, setErrors, value, acceptedFileTypes, wrapperProps, dropzoneRef) => {
18
+ const selectedFile = ref();
19
+ const fileAllocate = useFileAllocate(selectedFile, props);
20
+ const {
21
+ percent,
22
+ onDownloadProgress,
23
+ onUploadProgress
24
+ } = useFileProgress();
25
+ const request = {
26
+ requestOptions: {
27
+ ...props.requestOptions,
28
+ onDownloadProgress,
29
+ onUploadProgress
30
+ },
31
+ pathURL: props.uploadPathURL
32
+ };
33
+ const upload = useUploadLoader(request);
34
+ const validateFile = (file) => {
35
+ const acceptedTypes = fileAllocate.acceptFile.value;
36
+ if (acceptedTypes) {
37
+ const acceptedTypesList = acceptedTypes.split(",").map((type) => type.trim());
38
+ const fileExtension = file.name.toLowerCase().split(".").pop();
39
+ const isValidFileType = acceptedTypesList.some((acceptedType) => {
40
+ if (acceptedType.startsWith(".")) {
41
+ const extension = acceptedType.slice(1).toLowerCase();
42
+ return fileExtension === extension;
43
+ } else if (!acceptedType.includes("/") && !acceptedType.includes("*")) {
44
+ return fileExtension === acceptedType.toLowerCase();
45
+ }
46
+ if (acceptedType.endsWith("/*")) {
47
+ const baseType = acceptedType.slice(0, -2);
48
+ return file.type.startsWith(baseType + "/");
49
+ }
50
+ return file.type === acceptedType;
51
+ });
52
+ if (!isValidFileType) {
53
+ setErrors("\u0E1B\u0E23\u0E30\u0E40\u0E20\u0E17\u0E44\u0E1F\u0E25\u0E4C\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07 (\u0E23\u0E2D\u0E07\u0E23\u0E31\u0E1A\u0E40\u0E09\u0E1E\u0E32\u0E30 " + acceptedTypesList.join(", ") + ")");
54
+ return false;
55
+ }
56
+ }
57
+ const maxSizeBytes = (fileAllocate.acceptFileSizeKb.value || 0) * 1024;
58
+ if (file.size > maxSizeBytes) {
59
+ if (fileAllocate.isAcceptFileUseMb.value) {
60
+ setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeMb.value} MB`);
61
+ } else {
62
+ setErrors(`\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${fileAllocate.acceptFileSizeKb.value} KB`);
63
+ }
64
+ return false;
65
+ }
66
+ setErrors("");
67
+ return true;
68
+ };
69
+ const processFile = (file) => {
70
+ if (!validateFile(file)) return;
71
+ selectedFile.value = file;
72
+ emits("change", file);
73
+ const formData = new FormData();
74
+ formData.append(props.bodyKey || "file", file);
75
+ upload.run({
76
+ data: formData
77
+ });
78
+ };
79
+ const handleFileDrop = (files) => {
80
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly || !files?.length || !isEmpty.value) return;
81
+ const file = files[0];
82
+ if (file) {
83
+ processFile(file);
84
+ }
85
+ };
86
+ const fileDialog = useFileDialog({
87
+ accept: acceptedFileTypes.value || "",
88
+ directory: false,
89
+ multiple: false
90
+ });
91
+ const dropzone = useDropZone(dropzoneRef, {
92
+ onDrop: handleFileDrop,
93
+ // dataTypes: typeof props.accept === 'string' ? [props.accept] : props.accept,
94
+ multiple: false,
95
+ preventDefaultForUnhandled: false
96
+ });
97
+ const currentState = computed(() => {
98
+ if (value.value) return "success" /* SUCCESS */;
99
+ if (selectedFile.value && upload.status.value.isLoading) return "uploading" /* UPLOADING */;
100
+ if (selectedFile.value && upload.status.value.isError) return "error" /* ERROR */;
101
+ return "empty" /* EMPTY */;
102
+ });
103
+ const isEmpty = computed(() => currentState.value === "empty" /* EMPTY */);
104
+ const isUploading = computed(() => currentState.value === "uploading" /* UPLOADING */);
105
+ const isSuccess = computed(() => currentState.value === "success" /* SUCCESS */);
106
+ const isError = computed(() => currentState.value === "error" /* ERROR */);
107
+ const handleInputChange = (event) => {
108
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
109
+ const file = event.target.files?.[0];
110
+ if (file) {
111
+ processFile(file);
112
+ }
113
+ };
114
+ const handleOpenFile = () => {
115
+ if (wrapperProps.value.disabled || wrapperProps.value.readonly) return;
116
+ fileDialog.open();
117
+ };
118
+ const handleDeleteFile = () => {
119
+ fileDialog.reset();
120
+ upload.clear();
121
+ selectedFile.value = void 0;
122
+ onChange(void 0);
123
+ emits("delete");
124
+ };
125
+ const handleRetryUpload = () => {
126
+ if (selectedFile.value) {
127
+ const formData = new FormData();
128
+ formData.append(props.bodyKey || "file", selectedFile.value);
129
+ upload.run(formData);
130
+ }
131
+ };
132
+ const handlePreview = () => {
133
+ previewModal.open({
134
+ value: value.value
135
+ });
136
+ };
137
+ watch(
138
+ () => fileDialog.files.value,
139
+ (files) => {
140
+ if (files?.length) {
141
+ processFile(files[0]);
142
+ }
143
+ }
144
+ );
145
+ watch(
146
+ () => upload.status.value.isSuccess,
147
+ (isSuccess2) => {
148
+ if (isSuccess2 && upload.data.value) {
149
+ const fileValue = {
150
+ url: _get(upload.data.value, props.responseURL),
151
+ path: _get(upload.data.value, props.responsePath),
152
+ name: _get(upload.data.value, props.responseName) || selectedFile.value?.name,
153
+ size: Number(_get(upload.data.value, props.responseSize) || selectedFile.value?.size)
154
+ };
155
+ value.value = fileValue;
156
+ emits("success", fileValue);
157
+ }
158
+ }
159
+ );
160
+ watch(
161
+ () => upload.status.value.isError,
162
+ (isError2) => {
163
+ if (isError2) {
164
+ setErrors(StringHelper.getError(upload.status.value.errorData));
165
+ }
166
+ }
167
+ );
168
+ return {
169
+ // State
170
+ currentState,
171
+ isEmpty,
172
+ isUploading,
173
+ isSuccess,
174
+ isError,
175
+ selectedFile,
176
+ // Upload utilities
177
+ upload,
178
+ dropzone,
179
+ percent,
180
+ // Handlers
181
+ handleInputChange,
182
+ handleOpenFile,
183
+ handleDeleteFile,
184
+ handleRetryUpload,
185
+ handlePreview
186
+ };
187
+ };
@@ -20,7 +20,7 @@
20
20
  <p class="text-error-400">
21
21
  <Icon
22
22
  name="i-heroicons:exclamation-circle-solid"
23
- class="size-8 text-error-400"
23
+ class="text-error-400 size-8"
24
24
  />
25
25
  </p>
26
26
  </div>
@@ -1,80 +1,80 @@
1
1
  <template>
2
- <div
3
- v-if="!isHideCaption || !isHideBottomPagination"
4
- class="mb-4 text-gray-500"
5
- >
6
- <span class="font-bold">ผลลัพธ์ทั้งหมด:</span>
7
- จำนวน
8
- <span class="font-bold">{{ pageOptions?.totalCount || 0 }}</span>
9
- รายการ
10
- </div>
11
- <UTable
12
- :loading="status.isLoading"
13
- :columns="uTableCompatibleColumns"
14
- :rows="rawData"
15
- v-bind="$attrs"
16
- >
17
- <template #loading-state>
18
- <div class="flex h-60 items-center justify-center">
19
- <Icon
20
- name="i-svg-spinners:180-ring-with-bg"
21
- class="size-8 text-primary"
22
- />
23
- </div>
24
- </template>
25
- <template #empty-state>
26
- <Empty />
27
- </template>
28
- <template
29
- v-for="column in columns"
30
- #[`${column.accessorKey}-data`]="{ row }"
31
- :key="column.accessorKey"
32
- >
33
- <component
34
- :is="column.type === COLUMN_TYPES.COMPONENT ? column.component : column.type ? columnTypeComponents[column.type] : void 0"
35
- v-if="column.type === COLUMN_TYPES.COMPONENT || column.type && columnTypeComponents[column.type]"
36
- :value="transformValue(column, row)"
37
- :column="column"
38
- :row="row"
39
- />
40
- <ColumnText
41
- v-else
42
- :value="transformValue(column, row)"
43
- :column="column"
44
- :row="row"
45
- />
46
- </template>
47
-
48
- <template
49
- v-for="(_, slotName) in $slots"
50
- #[slotName]="scope"
51
- >
52
- <slot
53
- :name="slotName"
54
- v-bind="scope"
55
- />
56
- </template>
57
- </UTable>
58
- <div
59
- v-if="pageOptions"
60
- class="mt-4 flex justify-between px-3"
61
- >
62
- <p class="text-sm text-gray-500">
63
- ผลลัพธ์ {{ pageBetween }} ของ {{ totalCountWithComma }} รายการ
64
- </p>
65
- <Pagination
66
- v-if="pageOptions.totalPage > 1 && !isSimplePagination && !isHideBottomPagination"
67
- v-model="page"
68
- :page-count="pageOptions.limit"
69
- :total="pageOptions.totalCount"
70
- />
71
- <SimplePagination
72
- v-if="pageOptions.totalPage > 1 && isSimplePagination"
73
- v-model="page"
74
- :page-count="pageOptions.limit"
75
- :total="pageOptions.totalCount"
76
- />
77
- </div>
2
+ <div
3
+ v-if="!isHideCaption || !isHideBottomPagination"
4
+ class="mb-4 text-gray-500"
5
+ >
6
+ <span class="font-bold">ผลลัพธ์ทั้งหมด:</span>
7
+ จำนวน
8
+ <span class="font-bold">{{ pageOptions?.totalCount || 0 }}</span>
9
+ รายการ
10
+ </div>
11
+ <UTable
12
+ :loading="status.isLoading"
13
+ :columns="uTableCompatibleColumns"
14
+ :rows="rawData"
15
+ v-bind="$attrs"
16
+ >
17
+ <template #loading-state>
18
+ <div class="flex h-60 items-center justify-center">
19
+ <Icon
20
+ name="i-svg-spinners:180-ring-with-bg"
21
+ class="text-primary size-8"
22
+ />
23
+ </div>
24
+ </template>
25
+ <template #empty-state>
26
+ <Empty />
27
+ </template>
28
+ <template
29
+ v-for="column in columns"
30
+ #[`${column.accessorKey}-data`]="{ row }"
31
+ :key="column.accessorKey"
32
+ >
33
+ <component
34
+ :is="column.type === COLUMN_TYPES.COMPONENT ? column.component : column.type ? columnTypeComponents[column.type] : void 0"
35
+ v-if="column.type === COLUMN_TYPES.COMPONENT || column.type && columnTypeComponents[column.type]"
36
+ :value="transformValue(column, row)"
37
+ :column="column"
38
+ :row="row"
39
+ />
40
+ <ColumnText
41
+ v-else
42
+ :value="transformValue(column, row)"
43
+ :column="column"
44
+ :row="row"
45
+ />
46
+ </template>
47
+
48
+ <template
49
+ v-for="(_, slotName) in $slots"
50
+ #[slotName]="scope"
51
+ >
52
+ <slot
53
+ :name="slotName"
54
+ v-bind="scope"
55
+ />
56
+ </template>
57
+ </UTable>
58
+ <div
59
+ v-if="pageOptions"
60
+ class="mt-4 flex justify-between px-3"
61
+ >
62
+ <p class="text-sm text-gray-500">
63
+ ผลลัพธ์ {{ pageBetween }} ของ {{ totalCountWithComma }} รายการ
64
+ </p>
65
+ <Pagination
66
+ v-if="pageOptions.totalPage > 1 && !isSimplePagination && !isHideBottomPagination"
67
+ v-model="page"
68
+ :page-count="pageOptions.limit"
69
+ :total="pageOptions.totalCount"
70
+ />
71
+ <SimplePagination
72
+ v-if="pageOptions.totalPage > 1 && isSimplePagination"
73
+ v-model="page"
74
+ :page-count="pageOptions.limit"
75
+ :total="pageOptions.totalCount"
76
+ />
77
+ </div>
78
78
  </template>
79
79
 
80
80
  <script setup>
@@ -113,7 +113,7 @@ const props = defineProps({
113
113
  type: Boolean,
114
114
  default: false
115
115
  },
116
- isHideBottomPagination: {
116
+ isHidePagination: {
117
117
  type: Boolean,
118
118
  default: false
119
119
  },
@@ -25,8 +25,8 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
25
25
  type: PropType<ITableOptions["isSimplePagination"]>;
26
26
  default: boolean;
27
27
  };
28
- isHideBottomPagination: {
29
- type: PropType<ITableOptions["isHideBottomPagination"]>;
28
+ isHidePagination: {
29
+ type: PropType<ITableOptions["isHidePagination"]>;
30
30
  default: boolean;
31
31
  };
32
32
  isHideCaption: {
@@ -56,8 +56,8 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
56
56
  type: PropType<ITableOptions["isSimplePagination"]>;
57
57
  default: boolean;
58
58
  };
59
- isHideBottomPagination: {
60
- type: PropType<ITableOptions["isHideBottomPagination"]>;
59
+ isHidePagination: {
60
+ type: PropType<ITableOptions["isHidePagination"]>;
61
61
  default: boolean;
62
62
  };
63
63
  isHideCaption: {
@@ -68,8 +68,8 @@ declare const __VLS_component: import("vue").DefineComponent<import("vue").Extra
68
68
  onPageChange?: ((...args: any[]) => any) | undefined;
69
69
  }>, {
70
70
  isHideCaption: boolean | undefined;
71
- isHideBottomPagination: boolean | undefined;
72
71
  isSimplePagination: boolean | undefined;
72
+ isHidePagination: boolean | undefined;
73
73
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
74
74
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
75
75
  export default _default;
@@ -1,25 +1,25 @@
1
1
  <template>
2
- <Base
3
- v-bind="$attrs"
4
- :columns="options.columns"
5
- :raw-data="itemsByPage"
6
- :status="options.status"
7
- :page-options="pageOptions"
8
- :is-simple-pagination="isShowSimplePagination"
9
- :is-hide-bottom-pagination="options.isHideBottomPagination"
10
- :is-hide-caption="options.isHideCaption"
11
- @page-change="onPageChange"
12
- >
13
- <template
14
- v-for="(_, slot) of $slots"
15
- #[slot]="slotProps"
16
- >
17
- <slot
18
- :name="slot"
19
- v-bind="slotProps || {}"
20
- />
21
- </template>
22
- </Base>
2
+ <Base
3
+ v-bind="$attrs"
4
+ :columns="options.columns"
5
+ :raw-data="itemsByPage"
6
+ :status="options.status"
7
+ :page-options="pageOptions"
8
+ :is-simple-pagination="isShowSimplePagination"
9
+ :is-hide-pagination="options.isHidePagination"
10
+ :is-hide-caption="options.isHideCaption"
11
+ @page-change="onPageChange"
12
+ >
13
+ <template
14
+ v-for="(_, slot) of $slots"
15
+ #[slot]="slotProps"
16
+ >
17
+ <slot
18
+ :name="slot"
19
+ v-bind="slotProps || {}"
20
+ />
21
+ </template>
22
+ </Base>
23
23
  </template>
24
24
 
25
25
  <script setup>
@@ -13,7 +13,7 @@
13
13
  />
14
14
  </div>
15
15
  <div
16
- v-if="!options.isHideCaption || !options.isHideBottomPagination"
16
+ v-if="!options.isHideCaption || !options.isHidePagination"
17
17
  :class="theme.captionContainer({
18
18
  class: [ui?.captionContainer]
19
19
  })"
@@ -53,7 +53,7 @@
53
53
  name="error"
54
54
  >
55
55
  <div
56
- class="flex h-[200px] items-center justify-center text-2xl text-error-400"
56
+ class="text-error-400 flex h-[200px] items-center justify-center text-2xl"
57
57
  >
58
58
  {{ StringHelper.getError(options.status.errorData) }}
59
59
  </div>
@@ -91,6 +91,7 @@
91
91
  </UTable>
92
92
 
93
93
  <div
94
+ v-if="!options.isHidePagination"
94
95
  :class="theme.paginationContainer({
95
96
  class: [ui?.paginationContainer]
96
97
  })"
@@ -100,7 +101,7 @@
100
101
  class: [ui?.paginationInfo]
101
102
  })"
102
103
  >
103
- ผลลัพธ์ {{ pageBetween }} ของ {{ totalCountWithComma }} รายการ
104
+ {{ pageBetween }} รายการ จากทั้งหมด {{ totalCountWithComma }} รายการ
104
105
  </p>
105
106
  <Pagination
106
107
  v-model:page="page"
@@ -6,7 +6,7 @@ type Slot = TableSlots<any> & {
6
6
  };
7
7
  type __VLS_Slots = Slot;
8
8
  type __VLS_Props = {
9
- options: ITableOptions;
9
+ options: ITableOptions<any>;
10
10
  ui?: typeof tableTheme['slots'];
11
11
  };
12
12
  declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
@@ -19,9 +19,8 @@ export interface IBaseTableOptions<T extends Record<string, any> = Record<string
19
19
  rawData: T[];
20
20
  status: IStatus;
21
21
  columns: TableColumn<T>[];
22
- isHideBottomPagination?: boolean;
22
+ isHidePagination?: boolean;
23
23
  isHideCaption?: boolean;
24
- isHideTopPagination?: boolean;
25
24
  isSimplePagination?: boolean;
26
25
  }
27
26
  export interface ITableOptions<T extends Record<string, any> = Record<string, any>> extends IBaseTableOptions<T> {
@@ -3,14 +3,12 @@ export const useUploadLoader = (request) => {
3
3
  return useObjectLoader({
4
4
  method: "post",
5
5
  url: request.pathURL || "",
6
- getRequestOptions: (_data, opts) => ({
6
+ getRequestOptions: () => ({
7
7
  ...request.requestOptions,
8
8
  headers: {
9
9
  ...request.requestOptions.headers,
10
10
  "Content-Type": "multipart/form-data"
11
- },
12
- onDownloadProgress: opts.data?.onDownloadProgress,
13
- onUploadProgress: opts.data?.onUploadProgress
11
+ }
14
12
  })
15
13
  });
16
14
  };
@@ -1,4 +1,5 @@
1
1
  import type { Ref } from 'vue';
2
+ import type { AxiosProgressEvent } from 'axios';
2
3
  export declare const checkMaxSize: (file: File, acceptFileSize?: number) => boolean;
3
4
  export declare const checkFileType: (file: File, acceptFileType: string | string[]) => boolean;
4
5
  export declare const generateURL: (file: File) => string;
@@ -25,7 +26,7 @@ export declare const useFileSize: (size?: number) => {
25
26
  };
26
27
  export declare const useFileProgress: () => {
27
28
  percent: Ref<number, number>;
28
- onUploadProgress: (progressEvent: ProgressEvent) => void;
29
- onDownloadProgress: (progressEvent: ProgressEvent) => void;
29
+ onUploadProgress: (progressEvent: AxiosProgressEvent) => void;
30
+ onDownloadProgress: (progressEvent: AxiosProgressEvent) => void;
30
31
  };
31
32
  export declare const downloadFileFromURL: (url: string, filename?: string) => Promise<void>;
@@ -86,6 +86,10 @@ export const useFileSize = (size = 0) => {
86
86
  export const useFileProgress = () => {
87
87
  const percent = ref(0);
88
88
  const onUploadProgress = (progressEvent) => {
89
+ if (!progressEvent.total || progressEvent.total === 0) {
90
+ percent.value = 100;
91
+ return;
92
+ }
89
93
  percent.value = Number.parseFloat(
90
94
  StringHelper.withFixed(
91
95
  (Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.8
@@ -93,13 +97,13 @@ export const useFileProgress = () => {
93
97
  );
94
98
  };
95
99
  const onDownloadProgress = (progressEvent) => {
96
- if (progressEvent.total === 0) {
100
+ if (!progressEvent.total || progressEvent.total === 0) {
97
101
  percent.value = 100;
98
102
  return;
99
103
  }
100
104
  percent.value = Number.parseFloat(
101
105
  StringHelper.withFixed(
102
- (Math.floor(progressEvent.loaded * 100 / progressEvent.total) || 0) * 0.2 + 80
106
+ Math.floor(progressEvent.loaded * 100 / progressEvent.total) * 0.2 + 80
103
107
  )
104
108
  );
105
109
  };
@@ -13,6 +13,28 @@ export declare const uploadFileDropzoneTheme: {
13
13
  failedImgIcon: string;
14
14
  loadingIcon: string;
15
15
  labelIcon: string;
16
+ onLoadingWrapper: string;
17
+ onLoadingPlaceholderWrapper: string;
18
+ onLoadingPlaceholderIconClass: string;
19
+ onLoadingTextWrapper: string;
20
+ onLoadingLoadingIconClass: string;
21
+ onPreviewWrapper: string;
22
+ onPreviewPreviewImgWrapper: string;
23
+ onPreviewPreviewImgClass: string;
24
+ onPreviewPreviewFileClass: string;
25
+ onPreviewTextWrapper: string;
26
+ onFailedWrapper: string;
27
+ onFailedFailedImgWrapper: string;
28
+ onFailedFailedIconClass: string;
29
+ onFailedTextWrapper: string;
30
+ actionWrapper: string;
31
+ actionIconClass: string;
32
+ actionDeleteIconClass: string;
33
+ actionPreviewIcon: string;
34
+ actionDownloadIcon: string;
35
+ actionDeleteIcon: string;
36
+ actionRetryIcon: string;
37
+ actionRetryBtnClass: string;
16
38
  };
17
39
  variants: {
18
40
  dragover: {
@@ -20,5 +42,15 @@ export declare const uploadFileDropzoneTheme: {
20
42
  base: string;
21
43
  };
22
44
  };
45
+ disabled: {
46
+ true: {
47
+ base: string;
48
+ };
49
+ };
50
+ failed: {
51
+ true: {
52
+ base: string;
53
+ };
54
+ };
23
55
  };
24
56
  };
@@ -1,23 +1,59 @@
1
1
  export const uploadFileDropzoneTheme = {
2
2
  slots: {
3
- base: "relative w-full p-4 transition rounded-lg flex items-center justify-center ring-1 bg-white ring-gray-300",
3
+ base: "relative w-full text-base p-4 transition rounded-lg flex items-center justify-center ring-1 bg-white ring-gray-300",
4
4
  wrapper: "flex flex-col items-center w-full",
5
5
  disabled: "bg-gray-100 border-none grayscale cursor-not-allowed",
6
- failed: "border-danger",
6
+ failed: "border-error",
7
7
  placeholderWrapper: "py-4 flex flex-col items-center justify-center",
8
8
  placeholder: "text-gray-400 text-center font-light text-sm truncate",
9
9
  labelWrapper: "flex items-center space-x-2 text-gray-400 text-center",
10
10
  filePreviewIcon: "i-heroicons:document-text-solid",
11
- uploadIcon: "i-ph:cloud-arrow-up",
11
+ uploadIcon: "ri:upload-cloud-2-line",
12
12
  placeholderImgIcon: "i-material-symbols:imagesmode-outline",
13
13
  failedImgIcon: "i-material-symbols:imagesmode-outline",
14
14
  loadingIcon: "i-svg-spinners:180-ring-with-bg",
15
- labelIcon: "w-6 h-6 text-gray-400 text-center mb-4"
15
+ labelIcon: "size-6 text-gray-400 text-center mb-3",
16
+ // Loading state
17
+ onLoadingWrapper: "flex items-center space-x-4 w-full",
18
+ onLoadingPlaceholderWrapper: "flex-shrink-0",
19
+ onLoadingPlaceholderIconClass: "size-12 text-gray-400",
20
+ onLoadingTextWrapper: "flex-1 min-w-0 flex items-center justify-between",
21
+ onLoadingLoadingIconClass: "size-10 text-primary animate-spin",
22
+ // Preview state
23
+ onPreviewWrapper: "flex items-center space-x-4 rounded-lg w-full",
24
+ onPreviewPreviewImgWrapper: "flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden bg-gray-100",
25
+ onPreviewPreviewImgClass: "w-full h-full object-cover",
26
+ onPreviewPreviewFileClass: "size-8 text-gray-400 m-auto mt-4",
27
+ onPreviewTextWrapper: "flex-1 min-w-0 flex items-center justify-between",
28
+ // Failed state
29
+ onFailedWrapper: "flex items-start space-x-4 w-full rounded-lg",
30
+ onFailedFailedImgWrapper: "flex-shrink-0",
31
+ onFailedFailedIconClass: "size-12",
32
+ onFailedTextWrapper: "flex-1 min-w-0 flex items-start justify-between",
33
+ // Actions
34
+ actionWrapper: "flex items-center space-x-2",
35
+ actionIconClass: "size-6 text-dimmed hover:text-dimmed-600 cursor-pointer transition-colors",
36
+ actionDeleteIconClass: "size-6 text-(--ui-color-error-500) hover:text-(--ui-color-error-600) cursor-pointer transition-colors",
37
+ actionPreviewIcon: "ic:outline-remove-red-eye",
38
+ actionDownloadIcon: "material-symbols:download",
39
+ actionDeleteIcon: "material-symbols:delete",
40
+ actionRetryIcon: "stash:arrow-retry",
41
+ actionRetryBtnClass: "px-0"
16
42
  },
17
43
  variants: {
18
44
  dragover: {
19
45
  true: {
20
- base: "ring-primary bg-primary-50"
46
+ base: "ring-primary bg-(--ui-color-primary-50)"
47
+ }
48
+ },
49
+ disabled: {
50
+ true: {
51
+ base: "bg-gray-100 border-none grayscale cursor-not-allowed"
52
+ }
53
+ },
54
+ failed: {
55
+ true: {
56
+ base: "ring-(--ui-color-error-500) bg-(--ui-color-error-50)"
21
57
  }
22
58
  }
23
59
  }