quasar-ui-danx 0.4.1 → 0.4.3

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 (80) hide show
  1. package/dist/danx.es.js +7234 -6741
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +11 -5
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -1
  7. package/src/components/ActionTable/ActionTable.vue +31 -43
  8. package/src/components/ActionTable/Columns/ActionTableColumn.vue +19 -18
  9. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +15 -14
  10. package/src/components/ActionTable/Filters/{FilterFieldList.vue → FilterList.vue} +26 -26
  11. package/src/components/ActionTable/Filters/FilterableField.vue +28 -31
  12. package/src/components/ActionTable/Filters/index.ts +2 -2
  13. package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +71 -0
  14. package/src/components/ActionTable/Form/Fields/FieldLabel.vue +8 -13
  15. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +34 -33
  16. package/src/components/ActionTable/Form/Fields/MultiFileField.vue +48 -44
  17. package/src/components/ActionTable/Form/Fields/NumberField.vue +60 -59
  18. package/src/components/ActionTable/Form/Fields/SelectField.vue +124 -138
  19. package/src/components/ActionTable/Form/Fields/SelectWithChildrenField.vue +28 -33
  20. package/src/components/ActionTable/Form/Fields/SingleFileField.vue +15 -15
  21. package/src/components/ActionTable/Form/Fields/SliderNumberField.vue +45 -0
  22. package/src/components/ActionTable/Form/Fields/TextField.vue +47 -66
  23. package/src/components/ActionTable/Form/Fields/index.ts +2 -0
  24. package/src/components/ActionTable/Form/RenderedForm.vue +50 -13
  25. package/src/components/ActionTable/Form/Utilities/MaxLengthCounter.vue +17 -0
  26. package/src/components/ActionTable/Form/Utilities/index.ts +1 -0
  27. package/src/components/ActionTable/Form/index.ts +1 -0
  28. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +22 -16
  29. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +11 -11
  30. package/src/components/ActionTable/listControls.ts +104 -166
  31. package/src/components/ActionTable/listHelpers.ts +2 -3
  32. package/src/components/ActionTable/tableColumns.ts +53 -77
  33. package/src/components/AuditHistory/AuditHistoryItemValue.vue +26 -26
  34. package/src/components/PanelsDrawer/PanelsDrawer.vue +17 -4
  35. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +6 -11
  36. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +20 -20
  37. package/src/components/Utility/Dialogs/ConfirmActionDialog.vue +39 -0
  38. package/src/components/Utility/Dialogs/ConfirmDialog.vue +57 -117
  39. package/src/components/Utility/Dialogs/DialogLayout.vue +77 -0
  40. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +42 -36
  41. package/src/components/Utility/Dialogs/InfoDialog.vue +40 -80
  42. package/src/components/Utility/Dialogs/index.ts +1 -0
  43. package/src/components/Utility/Files/FilePreview.vue +76 -73
  44. package/src/components/Utility/Layouts/ContentDrawer.vue +24 -31
  45. package/src/components/Utility/Tools/ActionVnode.vue +3 -3
  46. package/src/components/Utility/Tools/RenderVnode.vue +20 -11
  47. package/src/components/Utility/Transitions/MaxHeightTransition.vue +26 -0
  48. package/src/components/Utility/Transitions/index.ts +1 -0
  49. package/src/config/index.ts +36 -31
  50. package/src/helpers/FileUpload.ts +295 -297
  51. package/src/helpers/FlashMessages.ts +80 -71
  52. package/src/helpers/actions.ts +102 -82
  53. package/src/helpers/download.ts +189 -189
  54. package/src/helpers/downloadPdf.ts +55 -52
  55. package/src/helpers/formats.ts +151 -109
  56. package/src/helpers/index.ts +2 -0
  57. package/src/helpers/multiFileUpload.ts +72 -58
  58. package/src/helpers/objectStore.ts +52 -0
  59. package/src/helpers/request.ts +70 -51
  60. package/src/helpers/routes.ts +29 -0
  61. package/src/helpers/storage.ts +7 -3
  62. package/src/helpers/utils.ts +47 -29
  63. package/src/styles/quasar-reset.scss +94 -68
  64. package/src/styles/themes/danx/dialogs.scss +47 -0
  65. package/src/styles/themes/danx/forms.scss +18 -0
  66. package/src/styles/themes/danx/index.scss +4 -0
  67. package/src/types/actions.d.ts +43 -0
  68. package/src/types/config.d.ts +15 -0
  69. package/src/types/controls.d.ts +99 -0
  70. package/src/types/dialogs.d.ts +32 -0
  71. package/src/types/fields.d.ts +20 -0
  72. package/src/types/files.d.ts +54 -0
  73. package/src/types/formats.d.ts +4 -0
  74. package/src/{components/ActionTable/Form/form.d.ts → types/forms.d.ts} +6 -0
  75. package/src/types/index.d.ts +12 -0
  76. package/src/types/requests.d.ts +13 -0
  77. package/src/types/shared.d.ts +15 -0
  78. package/src/types/tables.d.ts +27 -0
  79. package/types/index.d.ts +1 -1
  80. /package/src/components/ActionTable/Filters/{FilterFieldItem.vue → FilterItem.vue} +0 -0
@@ -6,6 +6,7 @@
6
6
  content-class="h-full"
7
7
  class="dx-panels-drawer"
8
8
  title=""
9
+ no-route-dismiss
9
10
  @update:show="$emit('close')"
10
11
  >
11
12
  <div class="flex flex-col flex-nowrap h-full">
@@ -33,15 +34,17 @@
33
34
  <div class="dx-panels-drawer-body flex-grow overflow-hidden h-full">
34
35
  <div class="flex items-stretch flex-nowrap h-full">
35
36
  <PanelsDrawerTabs
37
+ :key="'pd-tabs:' + activeItem.id"
36
38
  v-model="activePanel"
37
39
  :class="tabsClass"
38
40
  :panels="panels"
39
41
  @update:model-value="$emit('update:model-value', $event)"
40
42
  />
41
43
  <PanelsDrawerPanels
44
+ :key="'pd-panels:' + activeItem.id"
42
45
  :panels="panels"
43
46
  :active-panel="activePanel"
44
- :class="panelsClass"
47
+ :class="activePanelOptions?.class || panelsClass"
45
48
  />
46
49
  <div
47
50
  v-if="$slots['right-sidebar']"
@@ -55,16 +58,17 @@
55
58
  </ContentDrawer>
56
59
  </template>
57
60
  <script setup lang="ts">
58
- import { ref, watch } from "vue";
61
+ import { computed, onMounted, ref, watch } from "vue";
59
62
  import { XIcon as CloseIcon } from "../../svg";
60
- import { ActionPanel } from "../ActionTable";
63
+ import { ActionPanel, ActionTargetItem } from "../../types";
61
64
  import { ContentDrawer } from "../Utility";
62
65
  import PanelsDrawerPanels from "./PanelsDrawerPanels";
63
66
  import PanelsDrawerTabs from "./PanelsDrawerTabs";
64
67
 
65
68
  export interface Props {
66
69
  title?: string,
67
- modelValue?: string,
70
+ modelValue?: string | number,
71
+ activeItem?: ActionTargetItem;
68
72
  tabsClass?: string | object,
69
73
  panelsClass?: string | object,
70
74
  panels: ActionPanel[]
@@ -74,10 +78,19 @@ defineEmits(["update:model-value", "close"]);
74
78
  const props = withDefaults(defineProps<Props>(), {
75
79
  title: "",
76
80
  modelValue: null,
81
+ activeItem: null,
77
82
  tabsClass: "w-[13.5rem]",
78
83
  panelsClass: "w-[35.5rem]"
79
84
  });
80
85
 
81
86
  const activePanel = ref(props.modelValue);
87
+ const activePanelOptions = computed(() => props.panels.find((panel) => panel.name === activePanel.value));
82
88
  watch(() => props.modelValue, (value) => activePanel.value = value);
89
+
90
+ onMounted(() => {
91
+ // Resolve the default panel if a panel has not been selected
92
+ if (!activePanel.value && props.panels.length) {
93
+ activePanel.value = props.panels[0].name;
94
+ }
95
+ });
83
96
  </script>
@@ -16,17 +16,12 @@
16
16
  </QTabPanels>
17
17
  </template>
18
18
 
19
- <script setup>
19
+ <script setup lang="ts">
20
+ import { ActionPanel } from "../../types";
20
21
  import { RenderVnode } from "../Utility";
21
22
 
22
- defineProps({
23
- activePanel: {
24
- type: String,
25
- default: null
26
- },
27
- panels: {
28
- type: Array,
29
- required: true
30
- }
31
- });
23
+ defineProps<{
24
+ activePanel?: string | number,
25
+ panels: ActionPanel[]
26
+ }>();
32
27
  </script>
@@ -28,37 +28,37 @@
28
28
  </template>
29
29
  </QTabs>
30
30
  </template>
31
- <script setup>
31
+ <script setup lang="ts">
32
32
  import { QTab } from "quasar";
33
+ import { ActionPanel } from "../../types";
33
34
  import { RenderVnode } from "../Utility";
34
35
 
35
36
  defineEmits(["update:model-value"]);
36
- defineProps({
37
- modelValue: {
38
- type: String,
39
- default: "general"
40
- },
41
- panels: {
42
- type: Array,
43
- required: true
44
- }
37
+
38
+ interface Props {
39
+ modelValue?: string | number;
40
+ panels: ActionPanel[];
41
+ }
42
+
43
+ withDefaults(defineProps<Props>(), {
44
+ modelValue: "general"
45
45
  });
46
46
  </script>
47
47
 
48
48
  <style lang="scss" module="cls">
49
49
  .panel-tabs {
50
- @apply p-4 h-auto;
50
+ @apply p-4 h-auto;
51
51
 
52
- :global(.q-tab) {
53
- justify-content: start !important;
52
+ :global(.q-tab) {
53
+ justify-content: start !important;
54
54
 
55
- :global(.q-focus-helper), :global(.q-tab__indicator) {
56
- display: none;
57
- }
55
+ :global(.q-focus-helper), :global(.q-tab__indicator) {
56
+ display: none;
57
+ }
58
58
 
59
- :global(.q-tab__content) {
60
- @apply p-0;
61
- }
62
- }
59
+ :global(.q-tab__content) {
60
+ @apply p-0;
61
+ }
62
+ }
63
63
  }
64
64
  </style>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <ConfirmDialog
3
+ class="dx-confirm-action-dialog"
4
+ v-bind="props"
5
+ :confirm-text="confirmText || computedConfirmText"
6
+ :title="title || computedTitle"
7
+ :content="content || computedContentText"
8
+ @confirm="$emit('confirm')"
9
+ @close="$emit('close')"
10
+ >
11
+ <template
12
+ v-for="slotName in childSlots"
13
+ #[slotName]
14
+ >
15
+ <slot :name="slotName" />
16
+ </template>
17
+ <slot />
18
+ </ConfirmDialog>
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import { computed } from "vue";
23
+ import { fNameOrCount } from "../../../helpers";
24
+ import { ConfirmActionDialogProps } from "../../../types";
25
+ import { default as ConfirmDialog } from "./ConfirmDialog";
26
+
27
+ defineEmits(["confirm", "close"]);
28
+
29
+ const props = withDefaults(defineProps<ConfirmActionDialogProps>(), {
30
+ message: "Are you sure you want to"
31
+ });
32
+
33
+ const nameLabel = computed(() => fNameOrCount(props.target, props.label || props.action));
34
+ const computedTitle = computed(() => `Confirm ${props.action}`);
35
+ const computedConfirmText = computed(() => `${props.action}`);
36
+ const computedContentText = computed(() => `${props.message} ${props.action.toLowerCase()}${nameLabel.value ? " " + nameLabel.value : ""}?`);
37
+
38
+ const childSlots = computed(() => ["title", "subtitle", "default", "toolbar", "actions"]);
39
+ </script>
@@ -1,135 +1,75 @@
1
1
  <template>
2
- <QDialog
3
- :full-height="fullHeight"
4
- :full-width="fullWidth"
5
- :model-value="!!modelValue"
6
- :no-backdrop-dismiss="!backdropDismiss"
7
- :maximized="maximized"
8
- @update:model-value="onClose"
2
+ <DialogLayout
3
+ class="dx-confirm-dialog"
4
+ v-bind="layoutProps"
5
+ @close="onClose"
9
6
  >
10
- <QCard class="flex flex-col flex-nowrap">
11
- <QCardSection
12
- v-if="title || $slots.title"
13
- class="pl-6 pr-10 border-b border-gray-300"
14
- >
15
- <h3
16
- class="font-normal flex items-center"
17
- :class="titleClass"
18
- >
19
- <slot name="title">
20
- {{ title }}
21
- </slot>
22
- </h3>
23
- <div
24
- v-if="subtitle"
25
- class="mt-1 text-sm"
7
+ <template
8
+ v-for="slotName in childSlots"
9
+ #[slotName]
10
+ >
11
+ <slot :name="slotName" />
12
+ </template>
13
+ <slot />
14
+
15
+ <template #actions>
16
+ <div class="dx-dialog-button-cancel">
17
+ <QBtn
18
+ :label="cancelText"
19
+ class="dx-dialog-button"
20
+ @click="onClose"
26
21
  >
27
- {{ subtitle }}
28
- </div>
29
- </QCardSection>
30
- <QCardSection v-if="$slots.toolbar">
31
- <slot name="toolbar" />
32
- </QCardSection>
33
- <QCardSection
34
- v-if="content || $slots.default"
35
- class="px-6 bg-gray-100 flex-grow max-h-full overflow-y-auto"
36
- :class="contentClass"
37
- >
38
- <slot>{{ content }}</slot>
39
- </QCardSection>
40
- <div class="flex px-6 py-4 border-t border-gray-300">
41
- <div class="flex-grow">
42
- <QBtn
43
- :label="cancelText"
44
- class="action-btn btn-white-gray"
45
- @click="onClose"
46
- >
47
- <slot name="cancel-text" />
48
- </QBtn>
49
- </div>
50
- <slot name="actions" />
51
- <div v-if="!hideConfirm">
52
- <QBtn
53
- :label="$slots['confirm-text'] ? '' : confirmText"
54
- class="action-btn ml-4"
55
- :class="confirmClass"
56
- :loading="isSaving"
57
- :disable="disabled"
58
- data-testid="confirm-button"
59
- @click="onConfirm"
60
- >
61
- <slot name="confirm-text" />
62
- </QBtn>
63
- </div>
22
+ <slot name="cancel-text" />
23
+ </QBtn>
64
24
  </div>
65
- <a
66
- class="absolute top-0 right-0 p-4 text-black"
67
- @click="onClose"
25
+ <slot name="actions" />
26
+ <div
27
+ v-if="!hideConfirm"
28
+ class="dx-dialog-button-confirm"
68
29
  >
69
- <CloseIcon class="w-5" />
70
- </a>
71
- </QCard>
72
- </QDialog>
30
+ <QBtn
31
+ :label="$slots['confirm-text'] ? '' : confirmText"
32
+ class="dx-dialog-button"
33
+ :class="confirmClass"
34
+ :loading="isSaving"
35
+ :disable="disabled"
36
+ data-testid="confirm-button"
37
+ @click="onConfirm"
38
+ >
39
+ <slot name="confirm-text" />
40
+ </QBtn>
41
+ </div>
42
+ </template>
43
+ </DialogLayout>
73
44
  </template>
74
45
 
75
- <script setup>
76
- import { XIcon as CloseIcon } from "@heroicons/vue/outline";
46
+ <script setup lang="ts">
47
+ import { computed } from "vue";
48
+ import { ConfirmDialogProps } from "../../../types";
49
+ import DialogLayout from "./DialogLayout";
77
50
 
78
51
  const emit = defineEmits(["update:model-value", "confirm", "close"]);
79
- const props = defineProps({
80
- modelValue: { type: [String, Boolean, Object], default: true },
81
- title: {
82
- type: String,
83
- default: ""
84
- },
85
- titleClass: {
86
- type: String,
87
- default: ""
88
- },
89
- subtitle: {
90
- type: String,
91
- default: ""
92
- },
93
- content: {
94
- type: String,
95
- default: ""
96
- },
97
- backdropDismiss: Boolean,
98
- maximized: Boolean,
99
- fullWidth: Boolean,
100
- fullHeight: Boolean,
101
- disabled: Boolean,
102
- isSaving: Boolean,
103
- closeOnConfirm: Boolean,
104
- hideConfirm: Boolean,
105
- confirmText: {
106
- type: String,
107
- default: "Confirm"
108
- },
109
- cancelText: {
110
- type: String,
111
- default: "Cancel"
112
- },
113
- confirmClass: {
114
- type: String,
115
- default: "bg-blue-600 text-white"
116
- },
117
- contentClass: {
118
- type: String,
119
- default: ""
120
- }
52
+
53
+ const props = withDefaults(defineProps<ConfirmDialogProps>(), {
54
+ confirmText: "Confirm",
55
+ cancelText: "Cancel",
56
+ confirmClass: "",
57
+ contentClass: ""
121
58
  });
122
59
 
60
+ const layoutProps = computed(() => ({ ...props, disabled: undefined }));
61
+ const childSlots = computed(() => ["title", "subtitle", "toolbar"]);
62
+
123
63
  function onConfirm() {
124
- emit("confirm");
64
+ emit("confirm");
125
65
 
126
- if (props.closeOnConfirm) {
127
- emit("close");
128
- }
66
+ if (props.closeOnConfirm) {
67
+ emit("close");
68
+ }
129
69
  }
130
70
 
131
71
  function onClose() {
132
- emit("update:model-value", false);
133
- emit("close");
72
+ emit("update:model-value", false);
73
+ emit("close");
134
74
  }
135
75
  </script>
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <QDialog
3
+ class="dx-dialog"
4
+ :full-height="fullHeight"
5
+ :full-width="fullWidth"
6
+ :model-value="true"
7
+ :no-backdrop-dismiss="!backdropDismiss"
8
+ :maximized="maximized"
9
+ @update:model-value="onClose"
10
+ >
11
+ <QCard class="dx-dialog-card flex flex-col flex-nowrap">
12
+ <QCardSection
13
+ class="dx-dialog-header flex items-center"
14
+ >
15
+ <div class="flex-grow">
16
+ <h3
17
+ v-if="title || $slots.title"
18
+ class="dx-dialog-title flex items-center"
19
+ :class="titleClass"
20
+ >
21
+ <slot name="title">
22
+ {{ title }}
23
+ </slot>
24
+ </h3>
25
+ <div
26
+ v-if="subtitle || $slots.subtitle"
27
+ class="dx-dialog-subtitle"
28
+ >
29
+ <slot name="subtitle">
30
+ {{ subtitle }}
31
+ </slot>
32
+ </div>
33
+ </div>
34
+ <div>
35
+ <div
36
+ class="dx-close-button cursor-pointer"
37
+ @click="onClose"
38
+ >
39
+ <CloseIcon class="w-5" />
40
+ </div>
41
+ </div>
42
+ </QCardSection>
43
+ <QCardSection v-if="$slots.toolbar">
44
+ <slot name="toolbar" />
45
+ </QCardSection>
46
+ <QCardSection
47
+ v-if="content || $slots.default"
48
+ class="dx-dialog-content flex-grow max-h-full overflow-y-auto"
49
+ :class="contentClass"
50
+ >
51
+ <slot>{{ content }}</slot>
52
+ </QCardSection>
53
+ <div class="flex dx-dialog-actions">
54
+ <slot name="actions" />
55
+ </div>
56
+ </QCard>
57
+ </QDialog>
58
+ </template>
59
+
60
+ <script setup lang="ts">
61
+ import { XIcon as CloseIcon } from "@heroicons/vue/outline";
62
+ import { DialogLayoutProps } from "../../../types";
63
+
64
+ const emit = defineEmits(["close"]);
65
+
66
+ withDefaults(defineProps<DialogLayoutProps>(), {
67
+ title: "",
68
+ titleClass: "",
69
+ subtitle: "",
70
+ content: "",
71
+ contentClass: ""
72
+ });
73
+
74
+ function onClose() {
75
+ emit("close");
76
+ }
77
+ </script>
@@ -22,6 +22,7 @@
22
22
  :key="'file-' + file.id"
23
23
  :name="file.id"
24
24
  :img-src="getThumbUrl(file)"
25
+ class="bg-black"
25
26
  >
26
27
  <div :class="cls['slide-image']">
27
28
  <template v-if="isVideo(file)">
@@ -30,7 +31,7 @@
30
31
  controls
31
32
  >
32
33
  <source
33
- :src="file.url + '#t=0.1'"
34
+ :src="getPreviewUrl(file) + '#t=0.1'"
34
35
  :type="file.mime"
35
36
  >
36
37
  </video>
@@ -38,7 +39,7 @@
38
39
  <img
39
40
  v-else
40
41
  :alt="file.filename"
41
- :src="file.url"
42
+ :src="getPreviewUrl(file)"
42
43
  >
43
44
  </div>
44
45
  </QCarouselSlide>
@@ -56,54 +57,59 @@ import { XIcon as CloseIcon } from "../../../svg";
56
57
 
57
58
  defineEmits(["close"]);
58
59
  const props = defineProps({
59
- files: {
60
- type: Array,
61
- default: () => []
62
- },
63
- defaultSlide: {
64
- type: String,
65
- default: ""
66
- }
60
+ files: {
61
+ type: Array,
62
+ default: () => []
63
+ },
64
+ defaultSlide: {
65
+ type: String,
66
+ default: ""
67
+ }
67
68
  });
68
69
 
69
70
  const carousel = ref(null);
70
71
  const currentSlide = ref(props.defaultSlide);
71
72
  function isVideo(file) {
72
- return file.mime?.startsWith("video");
73
+ return file.mime?.startsWith("video");
73
74
  }
75
+
76
+ function getPreviewUrl(file) {
77
+ return file.transcodes?.compress?.url || file.blobUrl || file.url;
78
+ }
79
+
74
80
  function getThumbUrl(file) {
75
- if (file.thumb) {
76
- return file.thumb.url;
77
- } else if (isVideo(file)) {
78
- // Base64 encode a PlayIcon for the placeholder image
79
- return `data:image/svg+xml;base64,${btoa(
80
- `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M0 0h24v24H0z" fill="none"/><path d="M8 5v14l11-7z"/></svg>`
81
- )}`;
82
- } else {
83
- return file.url;
84
- }
81
+ if (file.thumb) {
82
+ return file.thumb.url;
83
+ } else if (isVideo(file)) {
84
+ // Base64 encode a PlayIcon for the placeholder image
85
+ return `data:image/svg+xml;base64,${btoa(
86
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M0 0h24v24H0z" fill="none"/><path d="M8 5v14l11-7z"/></svg>`
87
+ )}`;
88
+ } else {
89
+ return getPreviewUrl(file);
90
+ }
85
91
  }
86
92
  </script>
87
93
  <style module="cls" lang="scss">
88
94
  .slide-image {
89
- width: 100%;
90
- height: 100%;
91
- background: black;
92
- display: flex;
93
- justify-content: center;
94
- align-items: center;
95
+ width: 100%;
96
+ height: 100%;
97
+ background: black;
98
+ display: flex;
99
+ justify-content: center;
100
+ align-items: center;
95
101
 
96
- img {
97
- max-height: 100%;
98
- max-width: 100%;
99
- object-fit: contain;
100
- }
102
+ img {
103
+ max-height: 100%;
104
+ max-width: 100%;
105
+ object-fit: contain;
106
+ }
101
107
  }
102
108
 
103
109
  .carousel {
104
- :deep(.q-carousel__navigation--bottom) {
105
- position: relative;
106
- bottom: 8em;
107
- }
110
+ :deep(.q-carousel__navigation--bottom) {
111
+ position: relative;
112
+ bottom: 8em;
113
+ }
108
114
  }
109
115
  </style>