quasar-ui-danx 0.4.12 → 0.4.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. package/dist/danx.es.js +10975 -6424
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +137 -7
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -2
  7. package/src/components/ActionTable/ActionTable.vue +65 -63
  8. package/src/components/ActionTable/Form/ActionForm.vue +18 -12
  9. package/src/components/ActionTable/Form/RenderedForm.vue +86 -60
  10. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +80 -70
  11. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +29 -29
  12. package/src/components/ActionTable/listControls.ts +32 -31
  13. package/src/components/Navigation/NavigationMenu.vue +83 -40
  14. package/src/components/PanelsDrawer/PanelsDrawer.vue +20 -8
  15. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +3 -1
  16. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +17 -4
  17. package/src/components/Utility/Tools/RenderVnode.vue +5 -1
  18. package/src/components/Utility/Transitions/AutoHeightTransition.vue +21 -0
  19. package/src/components/Utility/Transitions/index.ts +1 -0
  20. package/src/config/index.ts +1 -0
  21. package/src/helpers/actions.ts +31 -20
  22. package/src/helpers/date.ts +2 -2
  23. package/src/helpers/formats.ts +55 -5
  24. package/src/helpers/objectStore.ts +7 -0
  25. package/src/helpers/request.ts +1 -1
  26. package/src/helpers/routes.ts +5 -0
  27. package/src/helpers/utils.ts +3 -3
  28. package/src/types/actions.d.ts +4 -7
  29. package/src/types/config.d.ts +3 -1
  30. package/src/types/controls.d.ts +5 -7
  31. package/src/types/forms.d.ts +20 -1
  32. package/src/types/shared.d.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quasar-ui-danx",
3
- "version": "0.4.12",
3
+ "version": "0.4.14",
4
4
  "author": "Dan <dan@flytedesk.com>",
5
5
  "description": "DanX Vue / Quasar component library",
6
6
  "license": "MIT",
@@ -57,7 +57,8 @@
57
57
  "danx-icon": "^1.0.2",
58
58
  "exifreader": "^4.21.1",
59
59
  "gsap": "^3.12.5",
60
- "luxon": "^3.4.4"
60
+ "luxon": "^3.4.4",
61
+ "yaml": "^2.4.5"
61
62
  },
62
63
  "browserslist": [
63
64
  "last 4 Chrome versions",
@@ -1,66 +1,66 @@
1
1
  <template>
2
- <div
3
- class="dx-action-table overflow-hidden"
4
- :class="{'dx-no-data': !hasData, 'dx-is-loading': loadingList || loadingSummary, 'dx-is-loading-list': loadingList}"
5
- >
6
- <ActionVnode />
7
- <QTable
8
- ref="actionTable"
9
- :selected="selectedRows"
10
- :pagination="pagination"
11
- :columns="tableColumns"
12
- :loading="loadingList || loadingSummary"
13
- :rows="pagedItems?.data || []"
14
- :binary-state-sort="false"
15
- selection="multiple"
16
- :rows-per-page-options="rowsPerPageOptions"
17
- class="sticky-column sticky-header w-full h-full !border-0"
18
- :color="color"
19
- @update:selected="$emit('update:selected-rows', $event)"
20
- @update:pagination="() => {}"
21
- @request="(e) => $emit('update:pagination', {...e.pagination, __sort: mapSortBy(e.pagination, tableColumns)})"
22
- >
23
- <template #no-data>
24
- <slot name="empty">
25
- <EmptyTableState :text="`There are no ${label.toLowerCase()} matching the applied filter`" />
26
- </slot>
27
- </template>
28
- <template #top-row>
29
- <TableSummaryRow
30
- v-if="hasData"
31
- :label="label"
32
- :item-count="summary?.count || 0"
33
- :selected-count="selectedRows.length"
34
- :sticky-colspan="summaryColSpan"
35
- :loading="loadingSummary"
36
- :summary="summary"
37
- :columns="tableColumns"
38
- @clear="$emit('update:selected-rows', [])"
39
- />
40
- </template>
41
- <template #header-cell="rowProps">
42
- <ActionTableHeaderColumn
43
- v-model="columnSettings"
44
- :row-props="rowProps"
45
- :name="name"
46
- @update:model-value="onUpdateColumnSettings"
47
- />
48
- </template>
49
- <template #body-cell="rowProps">
50
- <ActionTableColumn
51
- :key="rowProps.key"
52
- :row-props="rowProps"
53
- :settings="columnSettings[rowProps.col.name]"
54
- >
55
- <slot
56
- :column-name="rowProps.col.name"
57
- :row="rowProps.row"
58
- :value="rowProps.value"
59
- />
60
- </ActionTableColumn>
61
- </template>
62
- </QTable>
63
- </div>
2
+ <div
3
+ class="dx-action-table overflow-hidden"
4
+ :class="{'dx-no-data': !hasData, 'dx-is-loading': loadingList || loadingSummary, 'dx-is-loading-list': loadingList}"
5
+ >
6
+ <ActionVnode />
7
+ <QTable
8
+ ref="actionTable"
9
+ :selected="selectedRows"
10
+ :pagination="pagination"
11
+ :columns="tableColumns"
12
+ :loading="loadingList || loadingSummary"
13
+ :rows="pagedItems?.data || []"
14
+ :binary-state-sort="false"
15
+ :selection="selection"
16
+ :rows-per-page-options="rowsPerPageOptions"
17
+ class="sticky-column sticky-header w-full h-full !border-0"
18
+ :color="color"
19
+ @update:selected="$emit('update:selected-rows', $event)"
20
+ @update:pagination="() => {}"
21
+ @request="(e) => $emit('update:pagination', {...e.pagination, __sort: mapSortBy(e.pagination, tableColumns)})"
22
+ >
23
+ <template #no-data>
24
+ <slot name="empty">
25
+ <EmptyTableState :text="`There are no ${label.toLowerCase()} matching the applied filter`" />
26
+ </slot>
27
+ </template>
28
+ <template #top-row>
29
+ <TableSummaryRow
30
+ v-if="hasData"
31
+ :label="label"
32
+ :item-count="summary?.count || 0"
33
+ :selected-count="selectedRows.length"
34
+ :sticky-colspan="summaryColSpan"
35
+ :loading="loadingSummary"
36
+ :summary="summary"
37
+ :columns="tableColumns"
38
+ @clear="$emit('update:selected-rows', [])"
39
+ />
40
+ </template>
41
+ <template #header-cell="rowProps">
42
+ <ActionTableHeaderColumn
43
+ v-model="columnSettings"
44
+ :row-props="rowProps"
45
+ :name="name"
46
+ @update:model-value="onUpdateColumnSettings"
47
+ />
48
+ </template>
49
+ <template #body-cell="rowProps">
50
+ <ActionTableColumn
51
+ :key="rowProps.key"
52
+ :row-props="rowProps"
53
+ :settings="columnSettings[rowProps.col.name]"
54
+ >
55
+ <slot
56
+ :column-name="rowProps.col.name"
57
+ :row="rowProps.row"
58
+ :value="rowProps.value"
59
+ />
60
+ </ActionTableColumn>
61
+ </template>
62
+ </QTable>
63
+ </div>
64
64
  </template>
65
65
 
66
66
  <script setup lang="ts">
@@ -89,6 +89,7 @@ export interface Props {
89
89
  columns: TableColumn[];
90
90
  rowsPerPageOptions?: number[];
91
91
  summaryColSpan?: number;
92
+ selection: "multiple" | "single";
92
93
  }
93
94
 
94
95
  const props = withDefaults(defineProps<Props>(), {
@@ -97,7 +98,8 @@ const props = withDefaults(defineProps<Props>(), {
97
98
  summary: null,
98
99
  loadingSummary: false,
99
100
  rowsPerPageOptions: () => [10, 25, 50, 100],
100
- summaryColSpan: null
101
+ summaryColSpan: null,
102
+ selection: "multiple"
101
103
  });
102
104
 
103
105
  const actionTable = ref(null);
@@ -1,16 +1,19 @@
1
1
  <template>
2
- <div>
3
- <RenderedForm
4
- v-bind="renderedFormProps"
5
- v-model:values="input"
6
- empty-value=""
7
- :saving="action.isApplying"
8
- @update:values="action.trigger(target, input)"
9
- />
10
- </div>
2
+ <div>
3
+ <RenderedForm
4
+ v-bind="renderedFormProps"
5
+ v-model:values="input"
6
+ empty-value=""
7
+ :saved-at="target.updated_at"
8
+ :saving="action.isApplying"
9
+ @update:values="action.trigger(target, input)"
10
+ >
11
+ <slot />
12
+ </RenderedForm>
13
+ </div>
11
14
  </template>
12
15
  <script setup lang="ts">
13
- import { ref, watch } from "vue";
16
+ import { Ref, ref, watch } from "vue";
14
17
  import { ActionTargetItem, AnyObject, Form, ResourceAction } from "../../../types";
15
18
  import RenderedForm from "./RenderedForm.vue";
16
19
 
@@ -27,7 +30,10 @@ interface ActionFormProps {
27
30
  savingClass?: string;
28
31
  }
29
32
 
30
- const props = defineProps<ActionFormProps>();
33
+ const props = withDefaults(defineProps<ActionFormProps>(), {
34
+ fieldClass: "",
35
+ savingClass: undefined
36
+ });
31
37
  const renderedFormProps = {
32
38
  form: props.form,
33
39
  noLabel: props.noLabel,
@@ -39,7 +45,7 @@ const renderedFormProps = {
39
45
  savingClass: props.savingClass
40
46
  };
41
47
 
42
- const input: AnyObject = ref({ ...props.target });
48
+ const input: Ref<AnyObject> = ref({ ...props.target });
43
49
  const fieldStatus: AnyObject = {};
44
50
 
45
51
  // Only update field values from target changes when the field is not already being saved
@@ -62,32 +62,37 @@
62
62
  </QTab>
63
63
  </QTabs>
64
64
  </div>
65
- <div
65
+ <template
66
66
  v-for="(field, index) in mappedFields"
67
67
  :key="field.id"
68
- :class="{ 'mt-4': index > 0, [fieldClass]: true }"
69
68
  >
70
- <RenderVnode
71
- v-if="field.vnode"
72
- :vnode="field.vnode"
73
- :field="field"
74
- :props="getVnodeProps(field)"
75
- @update:model-value="onInput(field.name, $event)"
76
- />
77
- <Component
78
- :is="field.component"
79
- :key="field.name + '-' + currentVariation"
80
- :model-value="getFieldValue(field.name)"
81
- :field="field"
82
- :label="field.label || undefined"
83
- :no-label="noLabel"
84
- :show-name="showName"
85
- :clearable="field.clearable || clearable"
86
- :disable="disable"
87
- :readonly="readonly"
88
- @update:model-value="onInput(field.name, $event)"
89
- />
90
- </div>
69
+ <div
70
+ v-show="isFieldEnabled(field)"
71
+ :class="{ 'mt-4': index > 0, [fieldClass]: true }"
72
+ >
73
+ <RenderVnode
74
+ v-if="field.vnode"
75
+ :vnode="field.vnode"
76
+ :props="getVnodeProps(field)"
77
+ :params="fieldInputs"
78
+ @update:model-value="onInput(field.name, $event)"
79
+ />
80
+ <Component
81
+ :is="field.component"
82
+ :key="field.name + '-' + currentVariation"
83
+ :model-value="getFieldValue(field.name)"
84
+ :field="field"
85
+ :label="field.label || undefined"
86
+ :no-label="noLabel"
87
+ :show-name="showName"
88
+ :clearable="field.clearable || clearable"
89
+ :disable="disable"
90
+ :readonly="readonly"
91
+ @update:model-value="onInput(field.name, $event)"
92
+ />
93
+ </div>
94
+ </template>
95
+ <slot />
91
96
  <div
92
97
  v-if="savedAt"
93
98
  :class="savingClass"
@@ -133,10 +138,10 @@
133
138
  </template>
134
139
  <script setup lang="ts">
135
140
  import { ExclamationCircleIcon as MissingIcon, PencilIcon as EditIcon } from "@heroicons/vue/solid";
136
- import { computed, ref } from "vue";
141
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
137
142
  import { fDateTime, FlashMessages, incrementName, replace } from "../../../helpers";
138
143
  import { TrashIcon as RemoveIcon } from "../../../svg";
139
- import { AnyObject, Form, FormFieldValue } from "../../../types";
144
+ import { AnyObject, FormFieldValue, RenderedFormProps } from "../../../types";
140
145
  import { ConfirmDialog, RenderVnode } from "../../Utility";
141
146
  import {
142
147
  BooleanField,
@@ -150,22 +155,6 @@ import {
150
155
  WysiwygField
151
156
  } from "./Fields";
152
157
 
153
- export interface RenderedFormProps {
154
- values?: FormFieldValue[] | object | null;
155
- form: Form;
156
- noLabel?: boolean;
157
- showName?: boolean;
158
- disable?: boolean;
159
- readonly?: boolean;
160
- saving?: boolean;
161
- clearable?: boolean;
162
- emptyValue?: string | number | boolean;
163
- canModifyVariations?: boolean;
164
- fieldClass?: string;
165
- savingClass?: string;
166
- savedAt?: string;
167
- }
168
-
169
158
  const props = withDefaults(defineProps<RenderedFormProps>(), {
170
159
  values: null,
171
160
  emptyValue: undefined,
@@ -191,16 +180,39 @@ const FORM_FIELD_MAP = {
191
180
  const mappedFields = props.form.fields.map((field) => ({
192
181
  placeholder: `Enter ${field.label}`,
193
182
  ...field,
194
- component: field.component || FORM_FIELD_MAP[field.type],
195
- default: field.type === "BOOLEAN" ? false : ""
183
+ component: field.component || FORM_FIELD_MAP[field.type]
196
184
  }));
197
185
 
198
186
  const fieldResponses = computed(() => {
199
- if (!props.values) return [];
200
- if (Array.isArray(props.values)) return props.values;
201
- return Object.entries(props.values).map(([name, value]) => ({ name, value, variation: "" }));
187
+ const values = props.values;
188
+ if (!values) return [];
189
+ if (Array.isArray(values)) return values;
190
+
191
+ return Object.entries(values).filter((entry) => mappedFields.find(mf => mf.name === entry[0])).map(([name, value]) => ({
192
+ name,
193
+ value,
194
+ variation: ""
195
+ }));
196
+ });
197
+
198
+ const fieldInputs = computed(() => {
199
+ const inputs: AnyObject = {};
200
+ for (const field of mappedFields) {
201
+ inputs[field.name] = getFieldValue(field.name);
202
+ }
203
+ return inputs;
202
204
  });
203
205
 
206
+ function isFieldEnabled(field) {
207
+ if (field.enabled === undefined) return true;
208
+
209
+ if (typeof field.enabled === "function") {
210
+ return field.enabled(fieldInputs.value);
211
+ }
212
+
213
+ return field.enabled;
214
+ }
215
+
204
216
  function getVnodeProps(field) {
205
217
  return {
206
218
  modelValue: getFieldValue(field.name),
@@ -246,11 +258,38 @@ function onInput(name: string, value: any) {
246
258
  updateValues(newValues);
247
259
  }
248
260
 
261
+ function updateValues(values: FormFieldValue[]) {
262
+ let updatedValues: FormFieldValue[] | object = values;
263
+
264
+ if (!Array.isArray(props.values)) {
265
+ updatedValues = values.reduce((acc: AnyObject, v) => {
266
+ acc[v.name] = v.value;
267
+ return acc;
268
+ }, {});
269
+ }
270
+
271
+ emit("update:values", updatedValues);
272
+ }
273
+
274
+ onMounted(() => {
275
+ window.addEventListener("beforeunload", handleBeforeUnload);
276
+ });
277
+ onBeforeUnmount(() => {
278
+ window.removeEventListener("beforeunload", handleBeforeUnload);
279
+ });
280
+
281
+
282
+ function handleBeforeUnload(event: BeforeUnloadEvent) {
283
+ if (props.saving) {
284
+ return event.returnValue = "Changes are currently being saved. If you leave now, you might lose unsaved changes.";
285
+ }
286
+ }
287
+
249
288
  function createVariation(variation) {
250
289
  return props.form.fields.map((field) => ({
251
290
  variation,
252
291
  name: field.name,
253
- value: field.type === "BOOLEAN" ? false : null
292
+ value: field.default_value === undefined ? null : field.default_value
254
293
  }));
255
294
  }
256
295
 
@@ -287,19 +326,6 @@ function onChangeVariationName() {
287
326
  newVariationName.value = "";
288
327
  }
289
328
 
290
- function updateValues(values: FormFieldValue[]) {
291
- let updatedValues: FormFieldValue[] | object = values;
292
-
293
- if (!Array.isArray(props.values)) {
294
- updatedValues = values.reduce((acc: AnyObject, v) => {
295
- acc[v.name] = v.value;
296
- return acc;
297
- }, {});
298
- }
299
-
300
- emit("update:values", updatedValues);
301
- }
302
-
303
329
  function onRemoveVariation(name: string) {
304
330
  if (!name) return;
305
331
 
@@ -1,70 +1,72 @@
1
1
  <template>
2
- <div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full">
3
- <slot name="top" />
4
- <slot name="toolbar">
5
- <ActionToolbar
6
- :title="title"
7
- :refresh-button="refreshButton"
8
- :actions="actions?.filter(a => a.batch)"
9
- :action-target="controller.selectedRows.value"
10
- :exporter="controller.exportList"
11
- :loading="controller.isLoadingList.value || controller.isLoadingSummary.value"
12
- @refresh="controller.refreshAll"
13
- >
14
- <template #default>
15
- <slot name="action-toolbar" />
16
- </template>
17
- </ActionToolbar>
18
- </slot>
19
- <div class="flex flex-nowrap flex-grow overflow-hidden w-full">
20
- <slot name="filters">
21
- <CollapsableFiltersSidebar
22
- v-if="activeFilter"
23
- :name="controller.name"
24
- :show-filters="showFilters"
25
- :filters="filters"
26
- :active-filter="activeFilter"
27
- class="dx-action-table-filters"
28
- @update:active-filter="controller.setActiveFilter"
29
- />
30
- </slot>
31
- <slot>
32
- <ActionTable
33
- class="flex-grow"
34
- :pagination="controller.pagination.value"
35
- :selected-rows="controller.selectedRows.value"
36
- :label="controller.label"
37
- :name="controller.name"
38
- :class="tableClass"
39
- :summary="controller.summary.value"
40
- :loading-list="controller.isLoadingList.value"
41
- :loading-summary="controller.isLoadingSummary.value"
42
- :paged-items="controller.pagedItems.value"
43
- :columns="columns"
44
- @update:selected-rows="controller.setSelectedRows"
45
- @update:pagination="controller.setPagination"
46
- />
47
- </slot>
48
- <slot name="panels">
49
- <PanelsDrawer
50
- v-if="activeItem && panels"
51
- :title="panelTitle"
52
- :model-value="activePanel"
53
- :active-item="activeItem"
54
- :panels="panels"
55
- @update:model-value="panel => controller.activatePanel(activeItem, panel)"
56
- @close="controller.setActiveItem(null)"
57
- >
58
- <template #controls>
59
- <PreviousNextControls
60
- :is-loading="controller.isLoadingList.value"
61
- @next="controller.getNextItem"
62
- />
63
- </template>
64
- </PanelsDrawer>
65
- </slot>
66
- </div>
67
- </div>
2
+ <div class="flex flex-grow flex-col flex-nowrap overflow-hidden h-full">
3
+ <slot name="top" />
4
+ <slot name="toolbar">
5
+ <ActionToolbar
6
+ v-if="!hideToolbar"
7
+ :title="title"
8
+ :refresh-button="refreshButton"
9
+ :actions="actions?.filter(a => a.batch)"
10
+ :action-target="controller.selectedRows.value"
11
+ :exporter="controller.exportList"
12
+ :loading="controller.isLoadingList.value || controller.isLoadingSummary.value"
13
+ @refresh="controller.refreshAll"
14
+ >
15
+ <template #default>
16
+ <slot name="action-toolbar" />
17
+ </template>
18
+ </ActionToolbar>
19
+ </slot>
20
+ <div class="flex flex-nowrap flex-grow overflow-hidden w-full">
21
+ <slot name="filters">
22
+ <CollapsableFiltersSidebar
23
+ v-if="activeFilter"
24
+ :name="controller.name"
25
+ :show-filters="showFilters"
26
+ :filters="filters"
27
+ :active-filter="activeFilter"
28
+ class="dx-action-table-filters"
29
+ @update:active-filter="controller.setActiveFilter"
30
+ />
31
+ </slot>
32
+ <slot>
33
+ <ActionTable
34
+ class="flex-grow"
35
+ :pagination="controller.pagination.value"
36
+ :selected-rows="controller.selectedRows.value"
37
+ :label="controller.label"
38
+ :name="controller.name"
39
+ :class="tableClass"
40
+ :summary="controller.summary.value"
41
+ :loading-list="controller.isLoadingList.value"
42
+ :loading-summary="controller.isLoadingSummary.value"
43
+ :paged-items="controller.pagedItems.value"
44
+ :columns="columns"
45
+ :selection="selection"
46
+ @update:selected-rows="controller.setSelectedRows"
47
+ @update:pagination="controller.setPagination"
48
+ />
49
+ </slot>
50
+ <slot name="panels">
51
+ <PanelsDrawer
52
+ v-if="activeItem && panels"
53
+ :title="panelTitle"
54
+ :model-value="activePanel"
55
+ :active-item="activeItem"
56
+ :panels="panels"
57
+ @update:model-value="panel => controller.activatePanel(activeItem, panel)"
58
+ @close="controller.setActiveItem(null)"
59
+ >
60
+ <template #controls>
61
+ <PreviousNextControls
62
+ :is-loading="controller.isLoadingList.value"
63
+ @next="controller.getNextItem"
64
+ />
65
+ </template>
66
+ </PanelsDrawer>
67
+ </slot>
68
+ </div>
69
+ </div>
68
70
  </template>
69
71
  <script setup lang="ts">
70
72
  import { computed } from "vue";
@@ -75,10 +77,8 @@ import ActionTable from "../ActionTable.vue";
75
77
  import { CollapsableFiltersSidebar } from "../Filters";
76
78
  import { ActionToolbar } from "../Toolbars";
77
79
 
78
- const props = defineProps<{
80
+ export interface ActionTableLayoutProps {
79
81
  title?: string;
80
- refreshButton: boolean;
81
- showFilters: boolean;
82
82
  controller: ActionController;
83
83
  columns: TableColumn[];
84
84
  filters?: FilterGroup[];
@@ -87,7 +87,17 @@ const props = defineProps<{
87
87
  exporter?: () => Promise<void>;
88
88
  panelTitleField?: string;
89
89
  tableClass?: string;
90
- }>();
90
+ refreshButton?: boolean;
91
+ showFilters?: boolean;
92
+ hideToolbar?: boolean;
93
+ selection?: "multiple" | "single";
94
+ }
95
+
96
+ const props = withDefaults(defineProps<ActionTableLayoutProps>(), {
97
+ title: "",
98
+ tableClass: "",
99
+ selection: "multiple"
100
+ });
91
101
 
92
102
  const activeFilter = computed(() => props.controller.activeFilter.value);
93
103
  const activeItem = computed(() => props.controller.activeItem.value);
@@ -1,33 +1,33 @@
1
1
  <template>
2
- <div class="dx-action-toolbar flex items-center">
3
- <div class="flex-grow px-6">
4
- <slot name="title">
5
- <h4 v-if="title">
6
- {{ title }}
7
- </h4>
8
- </slot>
9
- </div>
10
- <div class="py-3 px-6 flex items-center flex-nowrap">
11
- <slot />
12
- <RefreshButton
13
- v-if="refreshButton"
14
- :loading="loading"
15
- @click="$emit('refresh')"
16
- />
17
- <ExportButton
18
- v-if="exporter"
19
- :exporter="exporter"
20
- class="ml-4"
21
- />
22
- <ActionMenu
23
- v-if="actions && actions.length > 0"
24
- class="ml-4 dx-batch-actions"
25
- :target="actionTarget"
26
- :actions="actions"
27
- />
28
- <slot name="after" />
29
- </div>
30
- </div>
2
+ <div class="dx-action-toolbar flex items-center">
3
+ <div class="flex-grow px-6">
4
+ <slot name="title">
5
+ <h4 v-if="title">
6
+ {{ title }}
7
+ </h4>
8
+ </slot>
9
+ </div>
10
+ <div class="py-3 flex items-center flex-nowrap">
11
+ <slot />
12
+ <RefreshButton
13
+ v-if="refreshButton"
14
+ :loading="loading"
15
+ @click="$emit('refresh')"
16
+ />
17
+ <ExportButton
18
+ v-if="exporter"
19
+ :exporter="exporter"
20
+ class="ml-4"
21
+ />
22
+ <ActionMenu
23
+ v-if="actions && actions.length > 0"
24
+ class="ml-4 dx-batch-actions"
25
+ :target="actionTarget"
26
+ :actions="actions"
27
+ />
28
+ <slot name="after" />
29
+ </div>
30
+ </div>
31
31
  </template>
32
32
  <script setup lang="ts">
33
33
  import { ActionTargetItem, AnyObject, ResourceAction } from "../../../types";