quasar-ui-danx 0.0.10 → 0.0.12

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 (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,318 @@
1
+ <template>
2
+ <div>
3
+ <q-select
4
+ ref="selectField"
5
+ v-bind="$props"
6
+ :model-value="selectedValue"
7
+ outlined
8
+ hide-dropdown-icon
9
+ dense
10
+ emit-value
11
+ :use-input="filterable"
12
+ :hide-selected="filterable && isShowing && !$props.multiple"
13
+ :input-debounce="100"
14
+ :options="filteredOptions"
15
+ option-label="label"
16
+ option-value="value"
17
+ placeholder=""
18
+ :input-class="{'is-hidden': !isShowing, [inputClass]: true}"
19
+ class="max-w-full"
20
+ @filter="onFilter"
21
+ @clear="onClear"
22
+ @popup-show="onShow"
23
+ @popup-hide="onHide"
24
+ @update:model-value="onUpdate"
25
+ >
26
+ <template #append>
27
+ <DropDownIcon
28
+ class="w-4 transition"
29
+ :class="isShowing ? 'rotate-180' : ''"
30
+ />
31
+ </template>
32
+ <template #selected>
33
+ <div
34
+ v-if="$props.multiple"
35
+ class="flex gap-y-1 overflow-hidden"
36
+ :class="{'flex-nowrap gap-y-0': chipLimit === 1, [selectionClass]: true}"
37
+ >
38
+ <template v-if="chipOptions.length > 0">
39
+ <q-chip
40
+ v-for="chipOption in chipOptions"
41
+ :key="'selected-' + chipOption.label"
42
+ class="!mr-1"
43
+ >{{ chipOption.label }}
44
+ </q-chip>
45
+ <q-chip
46
+ v-if="selectedOptions.length > chipOptions.length"
47
+ class="!mr-1"
48
+ >
49
+ +{{ selectedOptions.length - chipOptions.length }}
50
+ </q-chip>
51
+ </template>
52
+ <template v-else>
53
+ {{ placeholder }}
54
+ </template>
55
+ </div>
56
+ <div
57
+ v-else
58
+ :class="selectionClass"
59
+ >{{ selectedLabel }}
60
+ </div>
61
+ </template>
62
+ </q-select>
63
+ </div>
64
+ </template>
65
+ <script setup>
66
+ import { ChevronDownIcon as DropDownIcon } from '@heroicons/vue/outline';
67
+ import { QSelect } from 'quasar';
68
+ import { computed, isRef, nextTick, ref } from 'vue';
69
+
70
+ const emit = defineEmits(['update:model-value', 'search']);
71
+ const props = defineProps({
72
+ ...QSelect.props,
73
+ modelValue: {
74
+ type: [Array, String, Number, Object],
75
+ default: undefined
76
+ },
77
+ placeholder: {
78
+ type: String,
79
+ default: ''
80
+ },
81
+ selectionLabel: {
82
+ type: String,
83
+ default: null
84
+ },
85
+ chipLimit: {
86
+ type: Number,
87
+ default: 3
88
+ },
89
+ inputClass: {
90
+ type: String,
91
+ default: ''
92
+ },
93
+ selectionClass: {
94
+ type: String,
95
+ default: ''
96
+ },
97
+ options: {
98
+ type: Array,
99
+ default: () => []
100
+ },
101
+ filterable: Boolean,
102
+ filterFn: {
103
+ type: Function,
104
+ default: null
105
+ }
106
+ });
107
+
108
+ const selectField = ref(null);
109
+
110
+ // The filter applied to the dropdown list of options
111
+ const filter = ref(null);
112
+ // Is showing the dropdown list
113
+ const isShowing = ref(false);
114
+
115
+ /**
116
+ * The options formatted so each has a label, value and selectionLabel
117
+ * @type {ComputedRef<{selectionLabel: string, label: string, value: string|*}[]>}
118
+ */
119
+ const computedOptions = computed(() => {
120
+ let options = props.options;
121
+ if (props.placeholder && !props.multiple && !props.filterable) {
122
+ options = [{ label: props.placeholder, value: null }, ...props.options];
123
+ }
124
+ options = options.map((o) => {
125
+ let opt = isRef(o) ? o.value : o;
126
+ return {
127
+ label: resolveLabel(opt),
128
+ value: resolveValue(opt),
129
+ selectionLabel: resolveSelectionLabel(opt)
130
+ };
131
+ });
132
+ return options;
133
+ });
134
+
135
+ const filteredOptions = computed(() => {
136
+ if (filter.value && !props.filterFn) {
137
+ return computedOptions.value.filter(o => o.label.toLocaleLowerCase().indexOf(filter.value.toLowerCase()) > -1);
138
+ } else {
139
+ return computedOptions.value;
140
+ }
141
+ });
142
+
143
+ /**
144
+ * The value selected. Basically a wrapper to better handle null values (see onUpdate function for more details)
145
+ * @type {ComputedRef<unknown>}
146
+ */
147
+ const selectedValue = computed(() => {
148
+ if (props.multiple) {
149
+ const arrVal = Array.isArray(props.modelValue) ? props.modelValue : [];
150
+ return arrVal.map((v) => {
151
+ return v === null ? '__null__' : v;
152
+ }) || [];
153
+ } else {
154
+ return props.modelValue === null ? '__null__' : props.modelValue;
155
+ }
156
+ });
157
+
158
+ /**
159
+ * The selected options. The list of options from the computedOptions list that are selected as defined by the modelValue
160
+ * @type {ComputedRef<*>}
161
+ */
162
+ const selectedOptions = computed(() => {
163
+ let values = selectedValue.value;
164
+ if (!props.multiple) {
165
+ values = (values || values === 0) ? [values] : [];
166
+ }
167
+ return computedOptions.value.filter((o) => {
168
+ return values.includes(o.value) || values.map(v => typeof v === 'object' && v.id).includes(o.value?.id);
169
+ });
170
+ });
171
+
172
+ /**
173
+ * The label to display in the input field of the dropdown for a selected state, non-selected state or an active filtering state.
174
+ * NOTE: Only applies to single select (not multiselect)
175
+ * @type {ComputedRef<unknown>}
176
+ */
177
+ const selectedLabel = computed(() => {
178
+ if (props.filterable && isShowing.value) return '';
179
+
180
+ if (!selectedOptions.value || selectedOptions.value.length === 0) {
181
+ return props.placeholder || '(Select Option)';
182
+ }
183
+ return selectedOptions.value[0].selectionLabel;
184
+ });
185
+
186
+ /**
187
+ * The options to display as chips (limited by chipLimit prop)
188
+ * @type {ComputedRef<*>}
189
+ */
190
+ const chipOptions = computed(() => {
191
+ return selectedOptions.value.slice(0, props.chipLimit);
192
+ });
193
+
194
+ /**
195
+ * Resolve the label of the option. This will display as the text in the dropdown list
196
+ * @param option
197
+ * @returns {*|string}
198
+ */
199
+ function resolveLabel(option) {
200
+ if (typeof option === 'string') {
201
+ return option;
202
+ }
203
+ if (typeof props.optionLabel === 'string') {
204
+ return option[props.optionLabel];
205
+ }
206
+ if (typeof props.optionLabel === 'function') {
207
+ return props.optionLabel(option);
208
+ }
209
+ return option?.label;
210
+ }
211
+
212
+ /**
213
+ * Resolve the label to display in the input field of the dropdown when an option is selected
214
+ * NOTE: Does not apply to multiselect, single select only
215
+ * @param option
216
+ * @returns {*|{default: null, type: String | StringConstructor}|string}
217
+ */
218
+ function resolveSelectionLabel(option) {
219
+ if (typeof option === 'string') {
220
+ return option;
221
+ }
222
+ if (typeof props.selectionLabel === 'string') {
223
+ return option[props.selectionLabel];
224
+ }
225
+ if (typeof props.selectionLabel === 'function') {
226
+ return props.selectionLabel(option);
227
+ }
228
+ return option?.selectionLabel || option?.label;
229
+ }
230
+
231
+ /**
232
+ * Resolve the value of the option using either defaults or the provided optionValue prop
233
+ * @param option
234
+ * @returns {string|*|string}
235
+ */
236
+ function resolveValue(option) {
237
+ if (typeof option === 'string') {
238
+ return option;
239
+ }
240
+ let value = option.value;
241
+ if (typeof props.optionValue === 'string') {
242
+ value = option[props.optionValue];
243
+ } else if (typeof props.optionValue === 'function') {
244
+ value = props.optionValue(option);
245
+ }
246
+ // Note the __null__ special case here. See the onUpdate function for more details
247
+ return value === null ? '__null__' : value;
248
+ }
249
+
250
+ /**
251
+ * Handle the update event from the QSelect
252
+ * NOTE: casts all null values as a special __null__ string as the null character is handled the same as undefined by QSelect.
253
+ * SelectField differentiates between these 2 to provide a null value selected state vs undefined is no value selected.
254
+ * @param value
255
+ */
256
+ function onUpdate(value) {
257
+ if (Array.isArray(value)) {
258
+ value = value.map((v) => v === '__null__' ? null : v);
259
+ }
260
+ emit('update:model-value', value === '__null__' ? null : value);
261
+ }
262
+
263
+ /** XXX: This tells us when we should apply the filter. QSelect likes to trigger a new filter everytime you open the dropdown
264
+ * But most of the time when you open the dropdown it is already filtered and annoying to try and clear the previous filter
265
+ **/
266
+ const shouldFilter = ref(false);
267
+
268
+ /**
269
+ * Filter the options list. Do this asynchronously for the update so that the QSelect can update its internal state first, then update SelectField
270
+ * @param val
271
+ * @param update
272
+ */
273
+ async function onFilter(val, update) {
274
+ if (!props.filterFn) {
275
+ filter.value = val;
276
+ await nextTick(update);
277
+ } else {
278
+ update();
279
+ if (shouldFilter.value === false) return;
280
+ if (val !== null && val !== filter.value) {
281
+ filter.value = val;
282
+ if (props.filterFn) {
283
+ await props.filterFn(val);
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Clear the selected value using undefined. SelectField differentiates between null and undefined.
291
+ * See the onUpdate function for more details
292
+ */
293
+ function onClear() {
294
+ emit('update:model-value', undefined);
295
+ }
296
+
297
+ /**
298
+ * Handle behavior when showing the dropdown
299
+ */
300
+ function onShow() {
301
+ isShowing.value = true;
302
+
303
+ // XXX: See description on shouldFilter declaration. Only allow filtering after dropdown is ALREADY opened
304
+ shouldFilter.value = false;
305
+ nextTick(() => {
306
+ shouldFilter.value = true;
307
+ selectField.value.focus();
308
+ });
309
+ }
310
+
311
+ /**
312
+ * Handle behavior when hiding the dropdown
313
+ */
314
+ function onHide() {
315
+ isShowing.value = false;
316
+ shouldFilter.value = false;
317
+ }
318
+ </script>
@@ -0,0 +1,81 @@
1
+ <template>
2
+ <div>
3
+ <template v-if="!loading && !options.length">
4
+ <div class="text-gray-silver">No options available</div>
5
+ </template>
6
+ <SelectField
7
+ v-model="selectedOption"
8
+ :options="options"
9
+ :label="label"
10
+ :placeholder="placeholder"
11
+ :option-value="opt => opt"
12
+ :loading="loading"
13
+ @update:model-value="onSelectOption"
14
+ />
15
+ <div v-if="selectedOption">
16
+ <q-checkbox
17
+ v-for="child in selectedOption.children"
18
+ :key="child.id"
19
+ :model-value="selectedChildren.includes(child.id)"
20
+ :field="child"
21
+ class="mt-3"
22
+ @update:model-value="onSelectChild(child)"
23
+ >
24
+ <div>{{ child.label }}</div>
25
+ <div class="text-xs text-gray-silver">{{ child.name }}</div>
26
+ </q-checkbox>
27
+ </div>
28
+ </div>
29
+ </template>
30
+ <script setup>
31
+ import { remove } from '@ui/helpers/array';
32
+ import { ref, watch } from 'vue';
33
+ import SelectField from './SelectField';
34
+
35
+ const emit = defineEmits(['update:model-value']);
36
+ const props = defineProps({
37
+ modelValue: {
38
+ type: Array,
39
+ default: () => ([])
40
+ },
41
+ label: {
42
+ type: String,
43
+ default: 'Selection'
44
+ },
45
+ placeholder: {
46
+ type: String,
47
+ default: 'Select an option'
48
+ },
49
+ options: {
50
+ type: Array,
51
+ default: () => []
52
+ },
53
+ loading: Boolean
54
+ });
55
+
56
+ function resolveSelectedOption() {
57
+ if (props.modelValue?.length > 0) {
58
+ return props.options.find((option) => option.children.find(child => props.modelValue.includes(child.id)));
59
+ }
60
+
61
+ return null;
62
+ }
63
+ const selectedOption = ref(resolveSelectedOption());
64
+ const selectedChildren = ref(props.modelValue || []);
65
+ function onSelectChild(child) {
66
+ if (selectedChildren.value.includes(child.id)) {
67
+ selectedChildren.value = remove(selectedChildren.value, child.id);
68
+ } else {
69
+ selectedChildren.value.push(child.id);
70
+ }
71
+ emit('update:model-value', selectedChildren.value.length > 0 ? selectedChildren.value : undefined);
72
+ }
73
+ function onSelectOption() {
74
+ selectedChildren.value = [];
75
+ emit('update:model-value', undefined);
76
+ }
77
+ watch(() => props.modelValue, (value) => {
78
+ selectedOption.value = resolveSelectedOption();
79
+ selectedChildren.value = value || [];
80
+ });
81
+ </script>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <div
3
+ class="max-w-full relative overflow-auto"
4
+ :class="{'p-4 border rounded border-gray-medium text-center': !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 mt-2"
16
+ >
17
+ <a
18
+ class="text-blue-base"
19
+ @click="$refs.file.click()"
20
+ >Upload</a>
21
+ <a
22
+ v-if="uploadedFile"
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
+ @change="onFileSelected"
31
+ />
32
+ </div>
33
+
34
+ <ImagePreview
35
+ v-if="!readonly || uploadedFile"
36
+ class="w-32 cursor-pointer mt-2"
37
+ :class="{'border border-dashed border-blue-base': !uploadedFile, 'mx-auto': !readonly}"
38
+ :image="uploadedFile"
39
+ downloadable
40
+ @click="!disable && $refs.file.click()"
41
+ />
42
+ <div
43
+ v-else-if="readonly"
44
+ class="py-1"
45
+ >--
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup>
51
+ import { ImagePreview } from '@ui/components';
52
+ import { useSingleFileUpload } from '@ui/helpers/singleFileUpload';
53
+ import { onMounted } from 'vue';
54
+ import FieldLabel from './FieldLabel';
55
+
56
+ const emit = defineEmits(['update:model-value']);
57
+ const props = defineProps({
58
+ modelValue: {
59
+ type: [Object, String],
60
+ default: null
61
+ },
62
+ field: {
63
+ type: Object,
64
+ required: true
65
+ },
66
+ showName: Boolean,
67
+ disable: Boolean,
68
+ readonly: Boolean
69
+ });
70
+ const { onComplete, onDrop, onFileSelected, uploadedFile, onClear } = useSingleFileUpload();
71
+ onComplete(() => emit('update:model-value', uploadedFile.value));
72
+
73
+ onMounted(() => {
74
+ if (props.modelValue) {
75
+ uploadedFile.value = props.modelValue;
76
+ }
77
+ });
78
+ </script>
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div>
3
+ <q-input
4
+ v-if="!readonly"
5
+ :data-dusk="'text-field-' + field?.id"
6
+ :data-testid="'text-field-' + field?.id"
7
+ :placeholder="field?.placeholder"
8
+ outlined
9
+ dense
10
+ :disable="disabled"
11
+ :label-slot="!noLabel"
12
+ :input-class="inputClass"
13
+ :class="parentClass"
14
+ stack-label
15
+ :type="type"
16
+ :model-value="modelValue"
17
+ :debounce="debounce"
18
+ @keydown.enter="$emit('submit')"
19
+ @update:model-value="$emit('update:model-value', $event)"
20
+ >
21
+ <template #label>
22
+ <FieldLabel
23
+ :field="field"
24
+ :label="label"
25
+ :show-name="showName"
26
+ :class="labelClass"
27
+ />
28
+ </template>
29
+ </q-input>
30
+ <div v-if="readonly">
31
+ <LabelValueBlock
32
+ :label="label || field.label"
33
+ :value="modelValue"
34
+ />
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup>
40
+ import FieldLabel from './FieldLabel';
41
+ import LabelValueBlock from './LabelValueBlock';
42
+
43
+ defineEmits(['update:model-value', 'submit']);
44
+ defineProps({
45
+ modelValue: {
46
+ type: [String, Number],
47
+ default: ''
48
+ },
49
+ field: {
50
+ type: Object,
51
+ default: null
52
+ },
53
+ type: {
54
+ type: String,
55
+ default: 'text'
56
+ },
57
+ label: {
58
+ type: String,
59
+ default: null
60
+ },
61
+ labelClass: {
62
+ type: String,
63
+ default: 'text-sm text-gray-shadow'
64
+ },
65
+ parentClass: {
66
+ type: String,
67
+ default: ''
68
+ },
69
+ inputClass: {
70
+ type: String,
71
+ default: ''
72
+ },
73
+ noLabel: Boolean,
74
+ showName: Boolean,
75
+ disabled: Boolean,
76
+ readonly: Boolean,
77
+ debounce: {
78
+ type: [String, Number],
79
+ default: 0
80
+ }
81
+ });
82
+ </script>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <div>
3
+ <FieldLabel
4
+ v-if="!noLabel"
5
+ :field="field"
6
+ :show-name="showName"
7
+ class="text-sm font-semibold text-gray-shadow block mb-2"
8
+ />
9
+ <template v-if="readonly">
10
+ <div
11
+ class="border border-gray-300 rounded-md p-2 bg-gray-100"
12
+ v-html="modelValue"
13
+ />
14
+ </template>
15
+ <TinyMceEditor
16
+ v-else
17
+ class="mt-2"
18
+ :api-key="apiKey"
19
+ :disabled="disable"
20
+ :model-value="modelValue"
21
+ @update:model-value="$emit('update:model-value', $event)"
22
+ />
23
+ </div>
24
+ </template>
25
+
26
+ <script setup>
27
+ import { apiKey } from '@ui/vendor/tinymce-config';
28
+ import { default as TinyMceEditor } from '@tinymce/tinymce-vue';
29
+ import FieldLabel from './FieldLabel';
30
+
31
+ defineEmits(['update:model-value']);
32
+ defineProps({
33
+ modelValue: {
34
+ type: [String, Number],
35
+ default: null
36
+ },
37
+ field: {
38
+ type: Object,
39
+ required: true
40
+ },
41
+ noLabel: Boolean,
42
+ showName: Boolean,
43
+ disable: Boolean,
44
+ readonly: Boolean
45
+ });
46
+ </script>
@@ -0,0 +1,23 @@
1
+ export { default as BooleanField } from "./BooleanField.vue";
2
+ export { default as ConfirmPasswordField } from "./ConfirmPasswordField.vue";
3
+ export { default as DateField } from "./DateField.vue";
4
+ export { default as DateRangeField } from "./DateRangeField.vue";
5
+ export { default as DateTimeField } from "./DateTimeField.vue";
6
+ export { default as DateTimePicker } from "./DateTimePicker.vue";
7
+ export { default as EditableDiv } from "./EditableDiv.vue";
8
+ export { default as FieldLabel } from "./FieldLabel.vue";
9
+ export { default as FileUploadButton } from "./FileUploadButton.vue";
10
+ export { default as InlineDateTimeField } from "./InlineDateTimeField.vue";
11
+ export { default as IntegerField } from "./IntegerField.vue";
12
+ export { default as LabeledInput } from "./LabeledInput.vue";
13
+ export { default as MultiFileField } from "./MultiFileField.vue";
14
+ export { default as MultiKeywordField } from "./MultiKeywordField.vue";
15
+ export { default as NewPasswordField } from "./NewPasswordField.vue";
16
+ export { default as NumberField } from "./NumberField.vue";
17
+ export { default as NumberRangeField } from "./NumberRangeField.vue";
18
+ export { default as SelectDrawer } from "./SelectDrawer.vue";
19
+ export { default as SelectField } from "./SelectField.vue";
20
+ export { default as SelectWithChildrenField } from "./SelectWithChildrenField.vue";
21
+ export { default as SingleFileField } from "./SingleFileField.vue";
22
+ export { default as TextField } from "./TextField.vue";
23
+ export { default as WysiwygField } from "./WysiwygField.vue";
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <div class="rendered-form">
3
+ <div
4
+ v-for="(field, index) in mappedFields"
5
+ :key="field.id"
6
+ :class="{ 'mt-4': index > 0 }"
7
+ >
8
+ <Component
9
+ :is="field.component"
10
+ v-model="fieldValues[field.name]"
11
+ :field="field"
12
+ :label="field.label || undefined"
13
+ :no-label="noLabel"
14
+ :show-name="showName"
15
+ :disable="disable"
16
+ :readonly="readonly"
17
+ @update:model-value="onInput(field.name, $event)"
18
+ />
19
+ </div>
20
+ </div>
21
+ </template>
22
+ <script setup>
23
+ import { reactive } from 'vue';
24
+ import {
25
+ BooleanField,
26
+ DateField,
27
+ DateRangeField,
28
+ IntegerField,
29
+ MultiFileField,
30
+ NumberField,
31
+ SingleFileField,
32
+ TextField,
33
+ WysiwygField
34
+ } from './Fields';
35
+
36
+ const emit = defineEmits(['update:values']);
37
+ const props = defineProps({
38
+ values: {
39
+ type: Object,
40
+ default: null
41
+ },
42
+ fields: {
43
+ type: Array,
44
+ required: true
45
+ },
46
+ noLabel: Boolean,
47
+ showName: Boolean,
48
+ disable: Boolean,
49
+ readonly: Boolean
50
+ });
51
+
52
+ const FORM_FIELD_MAP = {
53
+ BOOLEAN: BooleanField,
54
+ DATE: DateField,
55
+ DATE_RANGE: DateRangeField,
56
+ INTEGER: IntegerField,
57
+ NUMBER: NumberField,
58
+ TEXT: TextField,
59
+ SINGLE_FILE: SingleFileField,
60
+ MULTI_FILE: MultiFileField,
61
+ WYSIWYG: WysiwygField
62
+ };
63
+
64
+ const mappedFields = props.fields.map((field) => ({
65
+ placeholder: `Enter ${field.label}`,
66
+ ...field,
67
+ component: FORM_FIELD_MAP[field.type],
68
+ default: field.type === 'BOOLEAN' ? false : ''
69
+ }));
70
+
71
+ const fieldValues = reactive(props.values || {});
72
+
73
+ function onInput(key, value) {
74
+ emit('update:values', { ...fieldValues, [key]: value });
75
+ }
76
+ </script>