@bildvitta/quasar-ui-asteroid 3.16.0-beta.0 → 3.16.0-beta.10
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.
- package/package.json +2 -1
- package/src/components/actions/QasActions.vue +7 -0
- package/src/components/actions-menu/QasActionsMenu.vue +3 -2
- package/src/components/app-menu/QasAppMenu.vue +50 -6
- package/src/components/app-menu/QasAppMenu.yml +5 -0
- package/src/components/app-menu/private/PvAppMenuHelpChat.vue +222 -0
- package/src/components/btn-dropdown/QasBtnDropdown.vue +3 -3
- package/src/components/expansion-item/QasExpansionItem.vue +117 -12
- package/src/components/expansion-item/QasExpansionItem.yml +8 -0
- package/src/components/form-generator/QasFormGenerator.yml +4 -3
- package/src/components/form-view/QasFormView.vue +3 -1
- package/src/components/form-view/QasFormView.yml +3 -0
- package/src/components/grid-generator/QasGridGenerator.vue +2 -10
- package/src/components/grid-generator/QasGridGenerator.yml +4 -3
- package/src/components/header-actions/QasHeaderActions.vue +17 -4
- package/src/components/header-actions/QasHeaderActions.yml +6 -0
- package/src/components/label/QasLabel.vue +2 -2
- package/src/components/list-view/QasListView.vue +3 -1
- package/src/components/list-view/QasListView.yml +3 -0
- package/src/components/single-view/QasSingleView.vue +132 -81
- package/src/components/single-view/QasSingleView.yml +8 -0
- package/src/components/table-generator/QasTableGenerator.vue +16 -2
- package/src/components/table-generator/QasTableGenerator.yml +5 -0
- package/src/components/toggle-visibility/QasToggleVisibility.vue +79 -0
- package/src/components/toggle-visibility/QasToggleVisibility.yml +30 -0
- package/src/composables/private/index.js +3 -0
- package/src/composables/private/use-generator.js +26 -18
- package/src/composables/private/use-toggle-visibility.js +48 -0
- package/src/composables/private/use-view.js +186 -0
- package/src/mixins/view.js +12 -1
- package/src/vue-plugin.js +6 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bildvitta/quasar-ui-asteroid",
|
|
3
3
|
"description": "Asteroid",
|
|
4
|
-
"version": "3.16.0-beta.
|
|
4
|
+
"version": "3.16.0-beta.10",
|
|
5
5
|
"author": "Bild & Vitta <systemteam@bild.com.br>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "dist/asteroid.cjs.min.js",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"sass": "^1.62.1"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@bildvitta/composables": "^1.0.0-beta.7",
|
|
46
47
|
"@bildvitta/store-adapter": "^1.0.0",
|
|
47
48
|
"autonumeric": "^4.9.0",
|
|
48
49
|
"axios": "^1.4.0",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="q-mt-sm" :class="classes">
|
|
3
|
+
<div v-if="hasTertiarySlot" :class="columnClasses">
|
|
4
|
+
<slot name="tertiary" />
|
|
5
|
+
</div>
|
|
6
|
+
|
|
3
7
|
<div v-if="hasSecondarySlot" :class="columnClasses">
|
|
4
8
|
<slot name="secondary" />
|
|
5
9
|
</div>
|
|
@@ -52,6 +56,8 @@ const classes = computed(() => {
|
|
|
52
56
|
const isSmallOrFullWidth = screen.isSmall || props.useFullWidth
|
|
53
57
|
|
|
54
58
|
return [
|
|
59
|
+
'items-center',
|
|
60
|
+
|
|
55
61
|
// alinhamento
|
|
56
62
|
`justify-${props.align}`,
|
|
57
63
|
|
|
@@ -71,4 +77,5 @@ const columnClasses = computed(() => {
|
|
|
71
77
|
|
|
72
78
|
const hasPrimarySlot = computed(() => !!slots.primary)
|
|
73
79
|
const hasSecondarySlot = computed(() => !!slots.secondary)
|
|
80
|
+
const hasTertiarySlot = computed(() => !!slots.tertiary)
|
|
74
81
|
</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<qas-btn-dropdown v-bind="btnDropdownProps">
|
|
4
4
|
<q-list data-cy="actions-menu-list">
|
|
5
5
|
<slot v-for="(item, key) in formattedList.dropdownList" :item="item" :name="key">
|
|
6
|
-
<q-item v-bind="getItemProps(item)" :key="key" clickable data-cy="actions-menu-list-item" @click="setClickHandler(item)">
|
|
6
|
+
<q-item v-bind="getItemProps(item)" :key="key" active-class="primary" clickable data-cy="actions-menu-list-item" @click="setClickHandler(item)">
|
|
7
7
|
<q-item-section avatar>
|
|
8
8
|
<q-icon :name="item.icon" />
|
|
9
9
|
</q-item-section>
|
|
@@ -240,10 +240,11 @@ const { showTooltip, tooltipLabels } = useTooltips({ formattedList, fullList, pr
|
|
|
240
240
|
|
|
241
241
|
// functions
|
|
242
242
|
function getItemProps (item) {
|
|
243
|
-
const { disable, props: itemProps } = item
|
|
243
|
+
const { disable, props: itemProps, to } = item
|
|
244
244
|
|
|
245
245
|
return {
|
|
246
246
|
disable,
|
|
247
|
+
to,
|
|
247
248
|
...itemProps
|
|
248
249
|
}
|
|
249
250
|
}
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
</div>
|
|
31
31
|
|
|
32
32
|
<!-- List -->
|
|
33
|
-
<q-list v-if="props.items.length" class="q-mt-xl qas-app-menu__menu text-grey-10">
|
|
33
|
+
<q-list v-if="props.items.length" class="q-mt-xl qas-app-menu__menu text-grey-10" :class="menuClasses">
|
|
34
34
|
<template v-for="(menuItem, index) in props.items">
|
|
35
35
|
<div v-if="hasChildren(menuItem)" :key="`children-${index}`" class="qas-app-menu__content" :class="classes.content">
|
|
36
36
|
<q-item class="ellipsis items-center q-py-none qas-app-menu__item qas-app-menu__item--label-mini text-weight-bold">
|
|
@@ -75,9 +75,30 @@
|
|
|
75
75
|
</q-list>
|
|
76
76
|
</div>
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
78
|
+
<div v-if="showAppUser">
|
|
79
|
+
<!-- Chat Ajuda -->
|
|
80
|
+
<q-list v-if="helpChatLink" class="q-mt-xl">
|
|
81
|
+
<q-item class="q-mb-md text-primary" clickable>
|
|
82
|
+
<q-item-section avatar>
|
|
83
|
+
<q-icon name="sym_r_chat" />
|
|
84
|
+
</q-item-section>
|
|
85
|
+
|
|
86
|
+
<q-item-section>
|
|
87
|
+
<q-item-label>
|
|
88
|
+
<div class="ellipsis text-subtitle2">
|
|
89
|
+
Solicitar ajuda
|
|
90
|
+
</div>
|
|
91
|
+
</q-item-label>
|
|
92
|
+
</q-item-section>
|
|
93
|
+
|
|
94
|
+
<pv-app-menu-help-chat :link="props.helpChatLink" :mini-brand="props.miniBrand" @update:model-value="setHasOpenedHelpChat" />
|
|
95
|
+
</q-item>
|
|
96
|
+
</q-list>
|
|
97
|
+
|
|
98
|
+
<!-- User -->
|
|
99
|
+
<div class="full-width q-pb-lg q-px-lg">
|
|
100
|
+
<qas-app-user v-bind="defaultAppUserProps" />
|
|
101
|
+
</div>
|
|
81
102
|
</div>
|
|
82
103
|
</div>
|
|
83
104
|
</q-drawer>
|
|
@@ -85,6 +106,7 @@
|
|
|
85
106
|
</template>
|
|
86
107
|
|
|
87
108
|
<script setup>
|
|
109
|
+
import PvAppMenuHelpChat from './private/PvAppMenuHelpChat.vue'
|
|
88
110
|
import PvAppMenuDropdown from './private/PvAppMenuDropdown.vue'
|
|
89
111
|
import QasAppUser from '../app-user/QasAppUser.vue'
|
|
90
112
|
|
|
@@ -114,6 +136,11 @@ const props = defineProps({
|
|
|
114
136
|
type: String
|
|
115
137
|
},
|
|
116
138
|
|
|
139
|
+
helpChatLink: {
|
|
140
|
+
type: String,
|
|
141
|
+
default: ''
|
|
142
|
+
},
|
|
143
|
+
|
|
117
144
|
items: {
|
|
118
145
|
default: () => [],
|
|
119
146
|
type: Array
|
|
@@ -154,6 +181,7 @@ const router = useRouter()
|
|
|
154
181
|
const rootRoute = router.hasRoute('Root') ? { name: 'Root' } : { path: '/' }
|
|
155
182
|
|
|
156
183
|
const hasOpenedMenu = ref(false)
|
|
184
|
+
const hasOpenedHelpChat = ref(false)
|
|
157
185
|
const isMini = ref(screen.isLarge)
|
|
158
186
|
const reRenderCount = ref(0)
|
|
159
187
|
|
|
@@ -180,9 +208,14 @@ const model = computed({
|
|
|
180
208
|
|
|
181
209
|
const behavior = computed(() => screen.untilLarge ? 'mobile' : 'desktop')
|
|
182
210
|
const drawerWidth = computed(() => screen.untilLarge ? 320 : 280)
|
|
183
|
-
const isMiniMode = computed(() => screen.isLarge && isMini.value && !hasOpenedMenu.value)
|
|
184
211
|
const normalizedBrand = computed(() => isMini.value ? props.miniBrand : props.brand)
|
|
185
212
|
|
|
213
|
+
const isMiniMode = computed(() => {
|
|
214
|
+
return screen.isLarge && isMini.value && !hasOpenedMenu.value && !hasOpenedHelpChat.value
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const menuClasses = computed(() => ({ 'qas-app-menu__menu--spaced': !props.helpChatLink }))
|
|
218
|
+
|
|
186
219
|
const classes = computed(() => {
|
|
187
220
|
return {
|
|
188
221
|
content: {
|
|
@@ -262,6 +295,10 @@ function onMouseEvent ({ type }) {
|
|
|
262
295
|
function setHasOpenedMenu (value) {
|
|
263
296
|
hasOpenedMenu.value = value
|
|
264
297
|
}
|
|
298
|
+
|
|
299
|
+
function setHasOpenedHelpChat (value) {
|
|
300
|
+
hasOpenedHelpChat.value = value
|
|
301
|
+
}
|
|
265
302
|
</script>
|
|
266
303
|
|
|
267
304
|
<style lang="scss" scoped>
|
|
@@ -335,8 +372,15 @@ function setHasOpenedMenu (value) {
|
|
|
335
372
|
// Media: untilLarge
|
|
336
373
|
@media (min-width: $breakpoint-sm-max) {
|
|
337
374
|
&__menu {
|
|
375
|
+
overflow-x: hidden;
|
|
376
|
+
|
|
377
|
+
&:not(&--spaced) {
|
|
378
|
+
max-height: calc(100vh - 365px);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
&__menu--spaced {
|
|
338
383
|
max-height: calc(100vh - 310px);
|
|
339
|
-
overflow-x: auto;
|
|
340
384
|
}
|
|
341
385
|
}
|
|
342
386
|
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<!-- O chat em si é renderizado em um iframe, o que QMenu, toda vez que fecha se auto destroy, então não podemos usar o
|
|
2
|
+
iframe dentro do QMenu, pois toda vez que um iframe é destruído ele perde o seu estado, e teria que iniciar um novo chat,
|
|
3
|
+
pra isto é criado um iframe no body, e posicionado com JS + CSS para ficar sobre o QMenu. -->
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<q-menu class="pv-app-menu-help-chat shadow-2" v-bind="menuProps">
|
|
7
|
+
<header class="q-pa-md">
|
|
8
|
+
<q-img class="q-mb-md" :src="props.miniBrand" width="36px" />
|
|
9
|
+
|
|
10
|
+
<h3 class="text-h3">
|
|
11
|
+
Bem-vindo!
|
|
12
|
+
</h3>
|
|
13
|
+
|
|
14
|
+
<div class="text-body1">
|
|
15
|
+
Deixe a sua mensagem para o suporte técnico da Nave.
|
|
16
|
+
</div>
|
|
17
|
+
</header>
|
|
18
|
+
|
|
19
|
+
<qas-box class="full-width pv-app-menu-help-chat__content relative-position" :class="boxClasses">
|
|
20
|
+
<div v-if="showIframe" ref="chatContent" class="full-width" />
|
|
21
|
+
|
|
22
|
+
<div v-else class="full-width self-end">
|
|
23
|
+
<div class="q-mb-sm text-subtitle2">
|
|
24
|
+
Estamos conectados
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="text-body2">
|
|
28
|
+
Normalmente responde em alguns minutos
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<qas-btn class="full-width q-mt-lg" label="Iniciar conversa" variant="primary" @click="initializeChat" />
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<q-inner-loading :showing="showInnerLoading">
|
|
35
|
+
<q-spinner color="grey" size="2rem" />
|
|
36
|
+
</q-inner-loading>
|
|
37
|
+
</qas-box>
|
|
38
|
+
</q-menu>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup>
|
|
42
|
+
import { useScreen } from '../../../composables'
|
|
43
|
+
|
|
44
|
+
import { onMounted, onUnmounted, ref, nextTick, computed } from 'vue'
|
|
45
|
+
|
|
46
|
+
defineOptions({ name: 'PvAppMenuHelpChat' })
|
|
47
|
+
|
|
48
|
+
const props = defineProps({
|
|
49
|
+
link: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: ''
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
miniBrand: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: ''
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const screen = useScreen()
|
|
61
|
+
|
|
62
|
+
// refs
|
|
63
|
+
const chatContent = ref(null)
|
|
64
|
+
const isOpened = ref(false)
|
|
65
|
+
const showIframe = ref(false)
|
|
66
|
+
const showInnerLoading = ref(false)
|
|
67
|
+
|
|
68
|
+
// computed
|
|
69
|
+
const boxClasses = computed(() => {
|
|
70
|
+
return {
|
|
71
|
+
flex: !showIframe.value,
|
|
72
|
+
'pv-app-menu-help-chat__content--is-opened': showIframe.value
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const menuProps = computed(() => {
|
|
77
|
+
return {
|
|
78
|
+
anchor: screen.isSmall ? 'top middle' : 'top right',
|
|
79
|
+
maxWidth: screen.isSmall ? '300px' : '400px',
|
|
80
|
+
self: screen.isSmall ? 'bottom middle' : 'top left',
|
|
81
|
+
touchPosition: !screen.isSmall,
|
|
82
|
+
|
|
83
|
+
onBeforeHide: hideIframe,
|
|
84
|
+
onShow
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// hooks
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Toda vez que a prop "behavior" do componente "QasAppMenu" é alterada, este componente
|
|
92
|
+
* é renderizado novamente, então é necessário esconder o iframe caso ele ja esteja aberto.
|
|
93
|
+
*/
|
|
94
|
+
onMounted(hideIframe)
|
|
95
|
+
|
|
96
|
+
onUnmounted(() => {
|
|
97
|
+
if (showIframe.value) {
|
|
98
|
+
window.removeEventListener('scroll', onScroll)
|
|
99
|
+
window.removeEventListener('resize', onScroll)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
// functions
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Define o estilo do iframe de acordo com a posição do "chatContent" dentro
|
|
107
|
+
* do QMenu.
|
|
108
|
+
*/
|
|
109
|
+
async function setIframeStyle () {
|
|
110
|
+
await nextTick()
|
|
111
|
+
|
|
112
|
+
const iframe = getIframe()
|
|
113
|
+
|
|
114
|
+
if (!iframe || !chatContent.value) return
|
|
115
|
+
|
|
116
|
+
const { bottom, left, top } = chatContent.value.getBoundingClientRect()
|
|
117
|
+
|
|
118
|
+
const width = chatContent.value.offsetWidth
|
|
119
|
+
|
|
120
|
+
const styles = {
|
|
121
|
+
display: 'block',
|
|
122
|
+
top: `${bottom + window.scrollY}px`,
|
|
123
|
+
left: `${left + window.scrollX}px`,
|
|
124
|
+
bottom: `${top}px`,
|
|
125
|
+
width: `${width}px`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Object.assign(iframe.style, styles)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onShow () {
|
|
132
|
+
isOpened.value = true
|
|
133
|
+
|
|
134
|
+
if (showIframe.value) setIframeStyle()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function hideIframe () {
|
|
138
|
+
isOpened.value = false
|
|
139
|
+
|
|
140
|
+
const iframe = getIframe()
|
|
141
|
+
|
|
142
|
+
if (!iframe) return
|
|
143
|
+
|
|
144
|
+
iframe.style.display = 'none'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function onScroll () {
|
|
148
|
+
if (isOpened.value) setIframeStyle()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createIframe () {
|
|
152
|
+
const iframe = document.createElement('iframe')
|
|
153
|
+
|
|
154
|
+
iframe.id = 'chat-iframe'
|
|
155
|
+
iframe.className = 'pv-app-menu-help-chat__iframe'
|
|
156
|
+
iframe.style.display = 'none'
|
|
157
|
+
iframe.src = props.link
|
|
158
|
+
|
|
159
|
+
document.body.appendChild(iframe)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getIframe () {
|
|
163
|
+
return document.getElementById('chat-iframe')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function initializeChat () {
|
|
167
|
+
createIframe()
|
|
168
|
+
|
|
169
|
+
window.addEventListener('scroll', onScroll)
|
|
170
|
+
window.addEventListener('resize', onScroll)
|
|
171
|
+
|
|
172
|
+
toggleIframe()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function toggleIframe () {
|
|
176
|
+
showIframe.value = !showIframe.value
|
|
177
|
+
|
|
178
|
+
showInnerLoading.value = true
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* É adicionado um loading por 3 motivos:
|
|
182
|
+
* - o iframe demora um pouco para ser carregado
|
|
183
|
+
* - o loading é um feedback visual para o usuário enquanto o iframe é carregado
|
|
184
|
+
* - não é possível adicionar uma animação aparecendo o iframe, então desta
|
|
185
|
+
* forma fica suavizado
|
|
186
|
+
*/
|
|
187
|
+
setTimeout(async () => {
|
|
188
|
+
showInnerLoading.value = false
|
|
189
|
+
|
|
190
|
+
await nextTick()
|
|
191
|
+
|
|
192
|
+
onShow()
|
|
193
|
+
}, 2000)
|
|
194
|
+
}
|
|
195
|
+
</script>
|
|
196
|
+
|
|
197
|
+
<style lang="scss">
|
|
198
|
+
.pv-app-menu-help-chat {
|
|
199
|
+
overflow-y: hidden;
|
|
200
|
+
|
|
201
|
+
&__content {
|
|
202
|
+
height: 260px;
|
|
203
|
+
background-color: var(--qas-background-color) !important;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
&__iframe {
|
|
207
|
+
border: 0;
|
|
208
|
+
border-radius: var(--qas-generic-border-radius);
|
|
209
|
+
outline: none;
|
|
210
|
+
position: absolute;
|
|
211
|
+
height: calc(260px - calc(var(--qas-spacing-md) * 2));
|
|
212
|
+
z-index: 9999;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Media: isSmall
|
|
216
|
+
@media (max-width: $breakpoint-sm-min) {
|
|
217
|
+
&__content--is-opened {
|
|
218
|
+
padding: var(--qas-spacing-sm) !important;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
</style>
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<div class="qas-btn-dropdown" :class="classes.parent">
|
|
3
3
|
<div v-if="hasButtons" :class="classes.list">
|
|
4
4
|
<div v-for="(buttonProps, key, index) in props.buttonsPropsList" :key="key">
|
|
5
|
-
<div class="flex">
|
|
6
|
-
<qas-btn :disable="props.disable" v-bind="buttonProps" variant="tertiary" @click="onClick">
|
|
5
|
+
<div class="flex no-wrap">
|
|
6
|
+
<qas-btn :disable="props.disable" v-bind="buttonProps" no-wrap variant="tertiary" @click="onClick">
|
|
7
7
|
<q-menu v-if="hasMenuOnLeftSide" v-model="isMenuOpened" anchor="bottom right" auto-close self="top right" @update:model-value="onUpdateMenuValue">
|
|
8
8
|
<div :class="classes.menuContent">
|
|
9
9
|
<slot />
|
|
@@ -91,7 +91,7 @@ const classes = computed(() => {
|
|
|
91
91
|
},
|
|
92
92
|
|
|
93
93
|
list: {
|
|
94
|
-
flex: !isSingleButton.value
|
|
94
|
+
'flex no-wrap': !isSingleButton.value
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
})
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="qas-expansion-item" :class="errorClasses">
|
|
3
|
-
<
|
|
4
|
-
<q-expansion-item header-class="text-bold q-mt-sm q-pa-none" :label="props.label">
|
|
2
|
+
<div ref="expansionItem" class="full-width qas-expansion-item" :class="errorClasses" v-bind="expansionProps.parent">
|
|
3
|
+
<component :is="component.is" class="qas-expansion-item__box">
|
|
4
|
+
<q-expansion-item header-class="text-bold q-mt-sm q-pa-none qas-expansion-item__header" :label="props.label" v-bind="expansionProps.item">
|
|
5
5
|
<template #header>
|
|
6
6
|
<slot name="header">
|
|
7
7
|
<div class="full-width">
|
|
8
8
|
<div class="items-center q-col-gutter-sm row">
|
|
9
9
|
<slot name="label">
|
|
10
|
-
<h5 class="col-auto text-h5
|
|
10
|
+
<h5 class="col-auto text-h5">
|
|
11
11
|
{{ props.label }}
|
|
12
12
|
</h5>
|
|
13
13
|
</slot>
|
|
@@ -18,17 +18,25 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
|
+
|
|
22
|
+
<div v-if="hasHeaderBottom" class="q-mt-sm">
|
|
23
|
+
<slot name="header-bottom" />
|
|
24
|
+
</div>
|
|
21
25
|
</div>
|
|
22
26
|
</slot>
|
|
23
27
|
</template>
|
|
24
28
|
|
|
25
|
-
<q-separator class="q-my-md" />
|
|
29
|
+
<q-separator v-if="hasHeaderSeparator" class="q-my-md" />
|
|
30
|
+
|
|
31
|
+
<div :class="contentClasses">
|
|
32
|
+
<slot name="content">
|
|
33
|
+
<qas-grid-generator v-if="hasGridGenerator" use-inline v-bind="gridGeneratorProps" />
|
|
34
|
+
</slot>
|
|
35
|
+
</div>
|
|
26
36
|
|
|
27
|
-
<
|
|
28
|
-
<qas-grid-generator v-if="hasGridGenerator" v-bind="gridGeneratorProps" use-inline />
|
|
29
|
-
</slot>
|
|
37
|
+
<q-separator v-if="hasBottomSeparator" class="q-mt-md" />
|
|
30
38
|
</q-expansion-item>
|
|
31
|
-
</
|
|
39
|
+
</component>
|
|
32
40
|
|
|
33
41
|
<div v-if="hasError" class="q-pt-sm qas-expansion-item__error-message text-caption text-negative">
|
|
34
42
|
{{ props.errorMessage }}
|
|
@@ -37,9 +45,14 @@
|
|
|
37
45
|
</template>
|
|
38
46
|
|
|
39
47
|
<script setup>
|
|
40
|
-
import
|
|
48
|
+
import QasBox from '../box/QasBox.vue'
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
import { computed, provide, inject, onMounted, ref, useAttrs } from 'vue'
|
|
51
|
+
|
|
52
|
+
defineOptions({
|
|
53
|
+
name: 'QasExpansionItem',
|
|
54
|
+
inheritAttrs: false
|
|
55
|
+
})
|
|
43
56
|
|
|
44
57
|
const props = defineProps({
|
|
45
58
|
badges: {
|
|
@@ -64,20 +77,112 @@ const props = defineProps({
|
|
|
64
77
|
gridGeneratorProps: {
|
|
65
78
|
type: Object,
|
|
66
79
|
default: () => ({})
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
useHeaderSeparator: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: undefined
|
|
67
85
|
}
|
|
68
86
|
})
|
|
69
87
|
|
|
88
|
+
const attrs = useAttrs()
|
|
89
|
+
|
|
90
|
+
provide('isExpansionItem', true)
|
|
91
|
+
|
|
92
|
+
// slots
|
|
93
|
+
const slots = defineSlots()
|
|
94
|
+
|
|
95
|
+
// refs
|
|
96
|
+
const expansionItem = ref(null)
|
|
97
|
+
const hasNextSibling = ref(false)
|
|
98
|
+
|
|
99
|
+
onMounted(setHasNextSibling)
|
|
100
|
+
|
|
101
|
+
// constants
|
|
102
|
+
const isNestedExpansionItem = inject('isExpansionItem', false)
|
|
103
|
+
const component = {
|
|
104
|
+
is: isNestedExpansionItem ? 'div' : QasBox
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// computed
|
|
70
108
|
const hasError = computed(() => props.error || !!props.errorMessage)
|
|
109
|
+
const hasGridGenerator = computed(() => !!Object.keys(props.gridGeneratorProps).length)
|
|
110
|
+
const hasBottomSeparator = computed(() => isNestedExpansionItem && hasNextSibling.value)
|
|
111
|
+
const hasHeaderBottom = computed(() => !!slots['header-bottom'])
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Verifica se o componente deve adicionar um separador no header.
|
|
115
|
+
*
|
|
116
|
+
* - Se a propriedade useHeaderSeparator for true, retorna separador.
|
|
117
|
+
* - Se a propriedade useHeaderSeparator for undefined, retorna separador apenas se não for um componente aninhado.
|
|
118
|
+
* - Se a propriedade useHeaderSeparator for false, não retorna separador.
|
|
119
|
+
*/
|
|
120
|
+
const hasHeaderSeparator = computed(() => {
|
|
121
|
+
return typeof props.useHeaderSeparator === 'undefined' ? !isNestedExpansionItem : props.useHeaderSeparator
|
|
122
|
+
})
|
|
71
123
|
|
|
72
124
|
const errorClasses = computed(() => ({ 'qas-expansion-item--error': hasError.value }))
|
|
73
125
|
|
|
74
|
-
const
|
|
126
|
+
const contentClasses = computed(() => {
|
|
127
|
+
return {
|
|
128
|
+
'q-mt-sm': isNestedExpansionItem,
|
|
129
|
+
'q-mt-md': !isNestedExpansionItem && !props.useHeaderSeparator
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const expansionProps = computed(() => {
|
|
134
|
+
const {
|
|
135
|
+
'onUpdate:modelValue': onUpdateModelValue,
|
|
136
|
+
onShow,
|
|
137
|
+
onBeforeShow,
|
|
138
|
+
onBeforeHide,
|
|
139
|
+
onAfterShow,
|
|
140
|
+
onAfterHide,
|
|
141
|
+
...propsPayload
|
|
142
|
+
} = attrs
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
parent: {
|
|
146
|
+
...propsPayload
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
item: {
|
|
150
|
+
onUpdateModelValue,
|
|
151
|
+
onShow,
|
|
152
|
+
onBeforeShow,
|
|
153
|
+
onBeforeHide,
|
|
154
|
+
onAfterShow,
|
|
155
|
+
onAfterHide
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// functions
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Caso o componente esteja dentro de um QasExpansionItem, verifica se existe um próximo irmão
|
|
164
|
+
* para adicionar um separador.
|
|
165
|
+
*/
|
|
166
|
+
function setHasNextSibling (value) {
|
|
167
|
+
if (!isNestedExpansionItem) return
|
|
168
|
+
|
|
169
|
+
const hasTextContentSibling = !!expansionItem.value.nextSibling?.textContent?.trim?.()
|
|
170
|
+
const hasElementSibling = !!expansionItem.value.nextElementSibling
|
|
171
|
+
|
|
172
|
+
hasNextSibling.value = hasElementSibling || hasTextContentSibling
|
|
173
|
+
}
|
|
75
174
|
</script>
|
|
76
175
|
|
|
77
176
|
<style lang="scss">
|
|
78
177
|
.qas-expansion-item {
|
|
79
178
|
$root: &;
|
|
80
179
|
|
|
180
|
+
// em alguns casos quando usado com grid, o espaçamento afetava o header, com z-index o problema é resolvido
|
|
181
|
+
&__header {
|
|
182
|
+
position: relative;
|
|
183
|
+
z-index: 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
81
186
|
&--error {
|
|
82
187
|
#{$root}__box {
|
|
83
188
|
border: 2px solid $negative;
|
|
@@ -24,10 +24,18 @@ props:
|
|
|
24
24
|
desc: Propriedades que serão repassadas para o QasGridGenerator.
|
|
25
25
|
type: Object
|
|
26
26
|
|
|
27
|
+
use-header-separator:
|
|
28
|
+
desc: Propriedade para forçar o QSeparator no header.
|
|
29
|
+
type: Boolean
|
|
30
|
+
default: undefined
|
|
31
|
+
|
|
27
32
|
slots:
|
|
28
33
|
header:
|
|
29
34
|
desc: 'Slot para substituir o conteúdo do header'
|
|
30
35
|
|
|
36
|
+
header-bottom:
|
|
37
|
+
desc: 'Slot para acessar o conteúdo que fica abaixo do header.'
|
|
38
|
+
|
|
31
39
|
label:
|
|
32
40
|
desc: 'Slot para substituir o conteúdo da label do header.'
|
|
33
41
|
|
|
@@ -10,6 +10,10 @@ props:
|
|
|
10
10
|
type: [Array, String, Object]
|
|
11
11
|
examples: ["[{ sm: 6, md: 12 }]", "{ name: { sm: 6, md: 12 } }", "12", "{ sm: 6, md: 12 }"]
|
|
12
12
|
|
|
13
|
+
common-columns:
|
|
14
|
+
desc: Usado quando precisa passar o mesmo valor para todas as colunas, sem especificar cada uma, ela é feita como um merge com a prop "columns".
|
|
15
|
+
examples: ["{ name: { sm: 6, md: 12 } }", "12"]
|
|
16
|
+
|
|
13
17
|
disable:
|
|
14
18
|
desc: Deixa os campos desabilitados enviando a prop "disable" para cada campo.
|
|
15
19
|
default: "false"
|
|
@@ -63,9 +67,6 @@ props:
|
|
|
63
67
|
default: []
|
|
64
68
|
type: Array
|
|
65
69
|
|
|
66
|
-
use-common-columns:
|
|
67
|
-
desc: Utilizado quando passar a estrutura da prop "columns" sendo um objeto onde seus breakpoints serão replicados para todos fields.
|
|
68
|
-
|
|
69
70
|
slots:
|
|
70
71
|
'field-[nome-da-chave]':
|
|
71
72
|
desc: Acessa o slot de um campo especifico.
|