quasar-ui-danx 0.0.10 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. package/package.json +8 -2
  2. package/src/components/ActionTable/ActionTable.vue +143 -0
  3. package/src/components/ActionTable/BatchActionMenu.vue +60 -0
  4. package/src/components/ActionTable/EmptyTableState.vue +33 -0
  5. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +36 -0
  6. package/src/components/ActionTable/Filters/FilterGroupItem.vue +28 -0
  7. package/src/components/ActionTable/Filters/FilterGroupList.vue +76 -0
  8. package/src/components/ActionTable/Filters/FilterListToggle.vue +50 -0
  9. package/src/components/ActionTable/Filters/FilterableField.vue +143 -0
  10. package/src/components/ActionTable/Filters/index.ts +5 -0
  11. package/src/components/ActionTable/Form/Fields/BooleanField.vue +37 -0
  12. package/src/components/ActionTable/Form/Fields/ConfirmPasswordField.vue +46 -0
  13. package/src/components/ActionTable/Form/Fields/DateField.vue +59 -0
  14. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +110 -0
  15. package/src/components/ActionTable/Form/Fields/DateTimeField.vue +50 -0
  16. package/src/components/ActionTable/Form/Fields/DateTimePicker.vue +59 -0
  17. package/src/components/ActionTable/Form/Fields/EditableDiv.vue +39 -0
  18. package/src/components/ActionTable/Form/Fields/FieldLabel.vue +32 -0
  19. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +78 -0
  20. package/src/components/ActionTable/Form/Fields/InlineDateTimeField.vue +44 -0
  21. package/src/components/ActionTable/Form/Fields/IntegerField.vue +26 -0
  22. package/src/components/ActionTable/Form/Fields/LabelValueBlock.vue +22 -0
  23. package/src/components/ActionTable/Form/Fields/LabeledInput.vue +63 -0
  24. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +91 -0
  25. package/src/components/ActionTable/Form/Fields/MultiKeywordField.vue +57 -0
  26. package/src/components/ActionTable/Form/Fields/NewPasswordField.vue +39 -0
  27. package/src/components/ActionTable/Form/Fields/NumberField.vue +94 -0
  28. package/src/components/ActionTable/Form/Fields/NumberRangeField.vue +140 -0
  29. package/src/components/ActionTable/Form/Fields/SelectDrawer.vue +136 -0
  30. package/src/components/ActionTable/Form/Fields/SelectField.vue +318 -0
  31. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +81 -0
  32. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +78 -0
  33. package/src/components/ActionTable/Form/Fields/TextField.vue +82 -0
  34. package/src/components/ActionTable/Form/Fields/WysiwygField.vue +46 -0
  35. package/src/components/ActionTable/Form/Fields/index.ts +23 -0
  36. package/src/components/ActionTable/Form/RenderedForm.vue +76 -0
  37. package/src/components/ActionTable/Form/index.ts +2 -0
  38. package/src/components/ActionTable/RenderComponentColumn.vue +22 -0
  39. package/src/components/ActionTable/TableSummaryRow.vue +95 -0
  40. package/src/components/ActionTable/index.ts +10 -0
  41. package/src/components/ActionTable/listActions.ts +362 -0
  42. package/src/components/ActionTable/listHelpers.ts +74 -0
  43. package/src/components/ActionTable/tableColumns.ts +72 -0
  44. package/src/components/DragAndDrop/HandleDraggable.vue +29 -29
  45. package/src/components/DragAndDrop/ListItemDraggable.vue +10 -10
  46. package/src/components/DragAndDrop/index.ts +0 -1
  47. package/src/components/DragAndDrop/listDragAndDrop.ts +1 -1
  48. package/src/components/Utility/CollapsableSidebar.vue +119 -0
  49. package/src/components/Utility/ContentDrawer.vue +70 -0
  50. package/src/components/Utility/Dialogs/ConfirmDialog.vue +132 -0
  51. package/src/components/Utility/Dialogs/FullScreenDialog.vue +46 -0
  52. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +105 -0
  53. package/src/components/Utility/Dialogs/InfoDialog.vue +92 -0
  54. package/src/components/Utility/Dialogs/InputDialog.vue +35 -0
  55. package/src/components/Utility/ImagePreview.vue +192 -0
  56. package/src/components/Utility/Popover/PopoverMenu.vue +64 -0
  57. package/src/components/Utility/Transitions/ListTransition.vue +50 -0
  58. package/src/components/Utility/Transitions/SlideTransition.vue +63 -0
  59. package/src/components/Utility/Transitions/StaggeredListTransition.vue +97 -0
  60. package/src/components/Utility/index.ts +11 -0
  61. package/src/components/index.ts +3 -0
  62. package/src/helpers/FileUpload.ts +295 -0
  63. package/src/helpers/FlashMessages.ts +79 -0
  64. package/src/helpers/array.ts +37 -0
  65. package/src/helpers/compatibility.ts +64 -0
  66. package/src/helpers/date.ts +5 -0
  67. package/src/helpers/download.ts +200 -0
  68. package/src/helpers/downloadPdf.ts +92 -0
  69. package/src/helpers/files.ts +52 -0
  70. package/src/helpers/formats.ts +183 -0
  71. package/src/helpers/http.ts +62 -0
  72. package/src/helpers/index.ts +12 -1
  73. package/src/helpers/multiFileUpload.ts +68 -0
  74. package/src/helpers/singleFileUpload.ts +54 -0
  75. package/src/helpers/storage.ts +8 -0
  76. package/src/index.esm.js +3 -4
  77. package/src/svg/FilterIcon.svg +7 -0
  78. package/src/svg/ImageIcon.svg +30 -0
  79. package/src/svg/PdfIcon.svg +21 -0
  80. package/src/svg/PercentIcon.svg +13 -0
  81. package/src/svg/TrashIcon.svg +15 -0
  82. package/src/svg/XIcon.svg +18 -0
  83. package/src/svg/index.ts +8 -0
  84. package/src/vendor/tinymce-config.ts +1 -0
  85. package/src/vue-plugin.js +7 -4
  86. package/tsconfig.json +14 -13
  87. package/src/components/DragAndDrop/Icons/index.ts +0 -2
  88. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleDotsIcon.svg +0 -0
  89. /package/src/{components/DragAndDrop/Icons → svg}/DragHandleIcon.svg +0 -0
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <div>
3
+ <div
4
+ v-if="label"
5
+ class="font-bold text-xs mb-2"
6
+ >
7
+ {{ label }}
8
+ </div>
9
+ <div class="flex items-center cursor-pointer">
10
+ <DateIcon class="w-5 text-blue-base" />
11
+ <div class="font-bold ml-3 hover:text-blue-base">
12
+ <template v-if="date">
13
+ {{ formattedDate }}
14
+ </template>
15
+ <template v-else>
16
+ -&nbsp;-
17
+ </template>
18
+ </div>
19
+ </div>
20
+ <q-popup-proxy>
21
+ <q-date
22
+ v-model="date"
23
+ @update:model-value="onSave"
24
+ />
25
+ </q-popup-proxy>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup>
30
+ import { CalendarIcon as DateIcon } from '@heroicons/vue/outline';
31
+ import { fDate, parseQDate } from '@ui/helpers/formats';
32
+ import { computed, ref, watch } from 'vue';
33
+
34
+ const emit = defineEmits(['update:model-value']);
35
+ const props = defineProps({
36
+ modelValue: {
37
+ type: [String, Object],
38
+ default: null
39
+ },
40
+ label: {
41
+ type: String,
42
+ default: null
43
+ }
44
+ });
45
+
46
+ const formattedDate = computed(() => {
47
+ if (props.modelValue) {
48
+ return fDate(parseQDate(props.modelValue || '0000-00-00'));
49
+ }
50
+ return null;
51
+ });
52
+
53
+ const date = ref(props.modelValue);
54
+ watch(() => props.modelValue, val => date.value = val);
55
+
56
+ function onSave() {
57
+ emit('update:model-value', date.value);
58
+ }
59
+ </script>
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <div>
3
+ <div
4
+ v-if="label"
5
+ class="font-bold text-xs mb-2"
6
+ >
7
+ {{ label }}
8
+ </div>
9
+ <template v-if="inline">
10
+ <q-date
11
+ v-model="dateRange"
12
+ range
13
+ class="reactive"
14
+ @update:model-value="onSave"
15
+ />
16
+ </template>
17
+ <template v-else>
18
+ <div class="flex items-center cursor-pointer">
19
+ <DateIcon class="w-5 text-blue-base" />
20
+ <div class="font-bold ml-3 hover:text-blue-base">
21
+ <template v-if="dateRangeValue">
22
+ {{ formattedDates.from }} - {{ formattedDates.to }}
23
+ </template>
24
+ <template v-else>
25
+ -&nbsp;-
26
+ </template>
27
+ </div>
28
+ </div>
29
+ <q-popup-proxy>
30
+ <q-date
31
+ v-model="dateRange"
32
+ range
33
+ @update:model-value="onSave"
34
+ />
35
+ </q-popup-proxy>
36
+ </template>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup>
41
+ import { CalendarIcon as DateIcon } from '@heroicons/vue/outline';
42
+ import { fDate, parseQDate, parseQDateTime } from '@ui/helpers/formats';
43
+ import { computed, ref, watch } from 'vue';
44
+
45
+ const emit = defineEmits(['update:model-value']);
46
+ const props = defineProps({
47
+ modelValue: {
48
+ type: Object,
49
+ default: null
50
+ },
51
+ label: {
52
+ type: String,
53
+ default: null
54
+ },
55
+ inline: Boolean,
56
+ withTime: Boolean
57
+ });
58
+
59
+ const formattedDates = computed(() => {
60
+ if (dateRangeValue.value) {
61
+ if (props.withTime) {
62
+ return {
63
+ from: fDate(parseQDateTime(dateRangeValue.value.from || '0000-00-00')),
64
+ to: fDate(parseQDateTime(dateRangeValue.value.to || '9999-12-31'))
65
+ };
66
+ }
67
+
68
+ return {
69
+ from: fDate(parseQDate(dateRangeValue.value.from || '0000-00-00')),
70
+ to: fDate(parseQDate(dateRangeValue.value.to || '9999-12-31'))
71
+ };
72
+ }
73
+ return {
74
+ from: null,
75
+ to: null
76
+ };
77
+ });
78
+
79
+ const dateRange = ref(toQDateValue(props.modelValue));
80
+ watch(() => props.modelValue, val => dateRange.value = toQDateValue(val));
81
+
82
+ function toQDateValue(val) {
83
+ if (val?.from && val?.to) {
84
+ return fDate(val.from) === fDate(val.to) ? val.from : val;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ const dateRangeValue = computed(() => {
90
+ let range = dateRange.value;
91
+
92
+ if (typeof range === 'string') {
93
+ range = {
94
+ from: range,
95
+ to: range
96
+ };
97
+ }
98
+
99
+ if (range?.from && range?.to && props.withTime) {
100
+ range.from += ' 00:00:00';
101
+ range.to += ' 23:59:59';
102
+ }
103
+
104
+ return range;
105
+ });
106
+
107
+ function onSave() {
108
+ emit('update:model-value', dateRangeValue.value);
109
+ }
110
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <div>
3
+ <q-input
4
+ :model-value="fLocalizedDateTime(modelValue)"
5
+ :color="color"
6
+ class="bg-white rounded overflow-hidden px-2 w-48"
7
+ dense
8
+ readonly
9
+ @click="isShowing = true"
10
+ >
11
+ <template #append>
12
+ <q-icon name="event" class="cursor-pointer">
13
+ <q-popup-proxy v-model="isShowing">
14
+ <DateTimePicker
15
+ v-model="dateTime"
16
+ @cancel="isShowing = false"
17
+ @save="onSave"
18
+ />
19
+ </q-popup-proxy>
20
+ </q-icon>
21
+ </template>
22
+ </q-input>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup>
27
+ import { fLocalizedDateTime } from '@ui/helpers/formats';
28
+ import { ref } from 'vue';
29
+ import DateTimePicker from './DateTimePicker';
30
+
31
+ const emit = defineEmits(['update:model-value']);
32
+ const props = defineProps({
33
+ modelValue: {
34
+ type: String,
35
+ required: true
36
+ },
37
+ color: {
38
+ type: String,
39
+ default: 'blue-base'
40
+ }
41
+ });
42
+
43
+ const isShowing = ref(false);
44
+ const dateTime = ref(props.modelValue);
45
+
46
+ function onSave() {
47
+ emit('update:model-value', dateTime.value);
48
+ isShowing.value = false;
49
+ }
50
+ </script>
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <q-popup-proxy
3
+ :model-value="true"
4
+ cover
5
+ transition-show="scale"
6
+ transition-hide="scale"
7
+ class="bg-transparent shadow-none flex items-stretch"
8
+ >
9
+ <q-date v-model="dateTime" :mask="mask" :color="color">
10
+ <div class="flex items-center justify-center">
11
+ <div v-if="nullable" class="flex-grow">
12
+ <q-btn label="Clear" color="blue-base" flat @click="dateTime = null" />
13
+ </div>
14
+ <div>
15
+ <q-btn
16
+ label="Cancel"
17
+ color="blue-base"
18
+ flat
19
+ @click="$emit('cancel')"
20
+ />
21
+ <q-btn label="Set" color="blue-base" flat @click="$emit('save')" />
22
+ </div>
23
+ </div>
24
+ </q-date>
25
+ <q-time v-model="dateTime" :mask="mask" :color="color" class="ml-3" />
26
+ </q-popup-proxy>
27
+ </template>
28
+ <script setup>
29
+ import { dbDateTime, localizedDateTime, remoteDateTime } from '@ui/helpers/formats';
30
+ import { computed } from 'vue';
31
+
32
+ const emit = defineEmits(['update:modelValue', 'save', 'cancel', 'clear']);
33
+ const props = defineProps({
34
+ modelValue: {
35
+ type: String,
36
+ default: null
37
+ },
38
+ mask: {
39
+ type: String,
40
+ default: 'YYYY-MM-DD HH:mm'
41
+ },
42
+ color: {
43
+ type: String,
44
+ default: 'blue-base'
45
+ },
46
+ nullable: Boolean
47
+ });
48
+
49
+ const dateTime = computed({
50
+ get: () => dbDateTime(localizedDateTime(props.modelValue)),
51
+ set(value) {
52
+ const newValue = value ? dbDateTime(remoteDateTime(value)) : null;
53
+
54
+ if (newValue || props.nullable) {
55
+ emit('update:modelValue', newValue);
56
+ }
57
+ }
58
+ });
59
+ </script>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <div
3
+ contenteditable
4
+ class="inline-block hover:bg-blue-light focus:bg-blue-light transition duration-300 outline-none"
5
+ @input="onInput"
6
+ >
7
+ {{ text }}
8
+ </div>
9
+ </template>
10
+
11
+ <script setup>
12
+ import { useDebounceFn } from "@vueuse/core";
13
+ import { ref } from "vue";
14
+
15
+ const emit = defineEmits(["update:model-value", "change"]);
16
+ const props = defineProps({
17
+ modelValue: {
18
+ type: String,
19
+ required: true
20
+ },
21
+ debounceDelay: {
22
+ type: Number,
23
+ default: 500
24
+ }
25
+ });
26
+
27
+ const text = ref(props.modelValue);
28
+
29
+ const debouncedChange = useDebounceFn(() => {
30
+ emit("update:model-value", text.value);
31
+ emit("change", text.value);
32
+ }, props.debounceDelay);
33
+
34
+ function onInput(e) {
35
+ text.value = e.target.innerText;
36
+ debouncedChange();
37
+ }
38
+
39
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <span>
3
+ <slot>
4
+ {{ labelText }}
5
+ <template v-if="showName">({{ field?.name }})</template>
6
+ </slot>
7
+ <span
8
+ v-if="requiredLabel"
9
+ class="text-red-dark ml-1 text-xs bottom-1 relative"
10
+ >{{ requiredLabel }}</span>
11
+ </span>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { computed } from "vue";
16
+
17
+ const props = defineProps({
18
+ field: {
19
+ type: Object,
20
+ default: null
21
+ },
22
+ label: {
23
+ type: String,
24
+ default: null
25
+ },
26
+ showName: Boolean,
27
+ required: Boolean
28
+ });
29
+
30
+ const labelText = computed(() => props.label || props.field?.label);
31
+ const requiredLabel = computed(() => props.field?.required_group || (props.required || props.field?.required ? "*" : ""));
32
+ </script>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <q-btn v-bind="$props" @click="$refs.fileUpload.click()">
3
+ <slot>
4
+ <PlusIcon class="w-5 mr-2" />
5
+ {{ text }}
6
+ </slot>
7
+
8
+ <input
9
+ ref="fileUpload"
10
+ data-testid="file-upload"
11
+ type="file"
12
+ :accept="geolocation ? 'image/*;capture=camera' : undefined"
13
+ :capture="geolocation ? 'environment' : undefined"
14
+ class="hidden"
15
+ multiple
16
+ @change="onAttachFiles"
17
+ />
18
+ </q-btn>
19
+ </template>
20
+ <script setup>
21
+ import { PlusIcon } from '@heroicons/vue/outline';
22
+ import { FileUpload } from '@ui/helpers';
23
+ import { QBtn } from 'quasar';
24
+ import { ref } from 'vue';
25
+
26
+ defineExpose({ upload });
27
+ const emit = defineEmits([
28
+ 'uploading',
29
+ 'file-progress',
30
+ 'file-complete',
31
+ 'complete'
32
+ ]);
33
+ const props = defineProps({
34
+ ...QBtn.props,
35
+ text: {
36
+ type: String,
37
+ default: 'Add File'
38
+ },
39
+ locationWaitMessage: {
40
+ type: String,
41
+ default: 'Waiting for location...'
42
+ },
43
+ geolocation: Boolean
44
+ });
45
+
46
+ const fileUpload = ref(null);
47
+
48
+ function upload() {
49
+ fileUpload.value.click();
50
+ }
51
+
52
+ /**
53
+ * Upload newly attached files and emit events for progress / completion
54
+ *
55
+ * @param files
56
+ * @returns {Promise<void>}
57
+ */
58
+ async function onAttachFiles({ target: { files } }) {
59
+ emit('uploading', files);
60
+ let fileUpload = new FileUpload(files)
61
+ .onProgress(({ file, progress }) => {
62
+ file.progress = progress;
63
+ emit('file-progress', file);
64
+ })
65
+ .onComplete(({ file, uploadedFile }) => {
66
+ emit('file-complete', { file, uploadedFile });
67
+ })
68
+ .onAllComplete(() => {
69
+ emit('complete', fileUpload.files);
70
+ });
71
+
72
+ if (props.geolocation) {
73
+ await fileUpload.resolveLocation(props.locationWaitMessage);
74
+ }
75
+
76
+ fileUpload.upload();
77
+ }
78
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <div class="inline-block">
3
+ <div
4
+ class="cursor-pointer py-2 hover:bg-blue-light flex items-center justify-end"
5
+ >
6
+ {{ fLocalizedDateTime(modelValue, { empty: 'Never' }) }}
7
+ <EditIcon class="w-4 font-bold ml-2 text-gray-base" />
8
+ <q-popup-edit
9
+ v-slot="scope"
10
+ :model-value="modelValue"
11
+ touch-position
12
+ :offset="[0, 20]"
13
+ class="bg-blue-base text-white !min-w-0"
14
+ >
15
+ <DateTimePicker
16
+ v-model="scope.value"
17
+ :nullable="nullable"
18
+ @save="onSave(scope)"
19
+ @cancel="scope.cancel"
20
+ />
21
+ </q-popup-edit>
22
+ </div>
23
+ </div>
24
+ </template>
25
+ <script setup>
26
+ import { PencilIcon as EditIcon } from '@heroicons/vue/solid';
27
+ import { fLocalizedDateTime } from '@ui/helpers/formats';
28
+ import DateTimePicker from './DateTimePicker';
29
+
30
+ const emit = defineEmits(['close', 'save', 'update:model-value']);
31
+ defineProps({
32
+ modelValue: {
33
+ type: String,
34
+ default: null
35
+ },
36
+ nullable: Boolean
37
+ });
38
+
39
+ function onSave(scope) {
40
+ emit('update:model-value', scope.value);
41
+ emit('save', scope.value);
42
+ scope.set();
43
+ }
44
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <NumberField
3
+ :field="field"
4
+ :precision="0"
5
+ :model-value="modelValue"
6
+ :show-name="showName"
7
+ @update:model-value="$emit('update:model-value', $event)"
8
+ />
9
+ </template>
10
+
11
+ <script setup>
12
+ import NumberField from './NumberField';
13
+
14
+ defineEmits(['update:model-value']);
15
+ defineProps({
16
+ modelValue: {
17
+ type: [String, Number],
18
+ required: true
19
+ },
20
+ field: {
21
+ type: Object,
22
+ required: true
23
+ },
24
+ showName: Boolean
25
+ });
26
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div>
3
+ <div class="text-xs font-bold">{{ label }}</div>
4
+ <div :class="{'mt-2': !dense, 'mt-1': dense, 'text-no-wrap': nowrap}">
5
+ <slot>{{ value || "-" }}</slot>
6
+ </div>
7
+ </div>
8
+ </template>
9
+ <script setup>
10
+ defineProps({
11
+ label: {
12
+ type: String,
13
+ required: true
14
+ },
15
+ value: {
16
+ type: [String, Number],
17
+ default: "-"
18
+ },
19
+ dense: Boolean,
20
+ nowrap: Boolean
21
+ });
22
+ </script>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div>
3
+ <label for="first_name" class="form-label">
4
+ <slot name="label">{{ label }}</slot>
5
+ </label>
6
+ <div class="mt-1">
7
+ <q-input
8
+ :model-value="modelValue"
9
+ :error-message="error"
10
+ :error="!!error"
11
+ no-error-icon
12
+ outlined
13
+ dense
14
+ :disable="disabled"
15
+ :readonly="disabled"
16
+ :type="type"
17
+ :name="name"
18
+ :placeholder="placeholder || label"
19
+ :required="required"
20
+ :rules="rules"
21
+ :lazy-rules="!!rules"
22
+ class="w-full"
23
+ @update:model-value="$emit('update:model-value', $event)"
24
+ />
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup>
30
+ defineEmits(['update:model-value']);
31
+ defineProps({
32
+ type: {
33
+ type: String,
34
+ default: 'text'
35
+ },
36
+ name: {
37
+ type: String,
38
+ required: true
39
+ },
40
+ label: {
41
+ type: String,
42
+ default: null
43
+ },
44
+ placeholder: {
45
+ type: String,
46
+ default: null
47
+ },
48
+ modelValue: {
49
+ type: [String, Number],
50
+ required: true
51
+ },
52
+ error: {
53
+ type: String,
54
+ default: null
55
+ },
56
+ required: Boolean,
57
+ disabled: Boolean,
58
+ rules: {
59
+ type: Array,
60
+ default: null
61
+ }
62
+ });
63
+ </script>
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <div
3
+ class="max-w-full relative overflow-auto"
4
+ :class="{'p-4 border rounded border-gray-medium': !readonly}"
5
+ @dragover.prevent
6
+ @drop.prevent="onDrop"
7
+ >
8
+ <FieldLabel
9
+ :field="field"
10
+ :show-name="showName"
11
+ class="text-sm font-semibold"
12
+ />
13
+ <div
14
+ v-if="!disable && !readonly"
15
+ class="text-sm my-2"
16
+ >
17
+ <a
18
+ class="text-blue-base"
19
+ @click="$refs.file.click()"
20
+ >Upload</a>
21
+ <a
22
+ v-if="uploadedFiles.length > 0"
23
+ class="ml-3 text-red-dark"
24
+ @click="onClear"
25
+ >Clear</a>
26
+ <input
27
+ ref="file"
28
+ class="hidden"
29
+ type="file"
30
+ multiple
31
+ @change="onFilesSelected"
32
+ />
33
+ </div>
34
+
35
+ <div class="max-w-[50em] flex items-stretch justify-start">
36
+ <ImagePreview
37
+ v-for="file in uploadedFiles"
38
+ :key="'file-upload-' + file.id"
39
+ class="w-32 m-2 cursor-pointer bg-neutral-plus-5"
40
+ :class="{'border border-dashed border-blue-base': !uploadedFiles.length}"
41
+ :image="file"
42
+ :related-files="uploadedFiles"
43
+ downloadable
44
+ :removable="!readonly && !disable"
45
+ @remove="onRemove(file)"
46
+ />
47
+ <ImagePreview
48
+ v-if="!disable && !readonly"
49
+ class="w-32 m-2 cursor-pointer border border-dashed border-blue-base"
50
+ disabled
51
+ @click="$refs.file.click()"
52
+ />
53
+ <div
54
+ v-if="readonly && uploadedFiles.length === 0"
55
+ class="p-1"
56
+ >
57
+ --
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </template>
62
+
63
+ <script setup>
64
+ import { ImagePreview } from '@ui/components';
65
+ import { useMultiFileUpload } from '@ui/helpers';
66
+ import { onMounted } from 'vue';
67
+ import FieldLabel from './FieldLabel';
68
+
69
+ const emit = defineEmits(['update:model-value']);
70
+ const props = defineProps({
71
+ modelValue: {
72
+ type: [Object, String],
73
+ default: null
74
+ },
75
+ field: {
76
+ type: Object,
77
+ required: true
78
+ },
79
+ showName: Boolean,
80
+ disable: Boolean,
81
+ readonly: Boolean
82
+ });
83
+
84
+ const { onComplete, onDrop, onFilesSelected, uploadedFiles, onClear, onRemove } = useMultiFileUpload();
85
+ onMounted(() => {
86
+ if (props.modelValue) {
87
+ uploadedFiles.value = props.modelValue;
88
+ }
89
+ });
90
+ onComplete(() => emit('update:model-value', uploadedFiles.value));
91
+ </script>