@bildvitta/quasar-ui-asteroid 3.17.0-beta.27-alpha.0 → 3.17.0-beta.28
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 +1 -2
- package/src/components/app-menu/QasAppMenu.vue +14 -63
- package/src/components/app-menu/QasAppMenu.yml +5 -5
- package/src/components/app-menu/private/PvAppMenuHelpChat.vue +222 -0
- package/src/components/badge/QasBadge.vue +3 -29
- package/src/components/checkbox/QasCheckbox.vue +40 -5
- package/src/components/checkbox/QasCheckbox.yml +13 -0
- package/src/components/field/QasField.vue +1 -1
- package/src/components/form-generator/QasFormGenerator.yml +2 -2
- package/src/components/grid-generator/QasGridGenerator.vue +3 -3
- package/src/components/grid-generator/QasGridGenerator.yml +2 -2
- package/src/components/grid-item/QasGridItem.vue +1 -1
- package/src/components/text-truncate/QasTextTruncate.vue +73 -13
- package/src/components/text-truncate/QasTextTruncate.yml +14 -3
- package/src/composables/private/use-generator.js +20 -5
- package/src/shared/badge-config.js +29 -0
- package/src/composables/private/use-auth-user.js +0 -17
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bildvitta/quasar-ui-asteroid",
|
|
3
3
|
"description": "Asteroid",
|
|
4
|
-
"version": "3.17.0-beta.
|
|
4
|
+
"version": "3.17.0-beta.28",
|
|
5
5
|
"author": "Bild & Vitta <systemteam@bild.com.br>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "dist/asteroid.cjs.min.js",
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"date-fns": "^2.30.0",
|
|
51
51
|
"debug": "^4.3.4",
|
|
52
52
|
"fuse.js": "^6.6.2",
|
|
53
|
-
"gleap": "^14.2.5",
|
|
54
53
|
"humps": "^2.0.1",
|
|
55
54
|
"lodash-es": "^4.17.21",
|
|
56
55
|
"pica": "^9.0.1",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
|
|
80
80
|
<div v-if="showAppUser">
|
|
81
81
|
<!-- Chat Ajuda -->
|
|
82
|
-
<q-list v-if="
|
|
83
|
-
<q-item class="q-mb-md text-primary" clickable
|
|
82
|
+
<q-list v-if="helpChatLink" class="q-mt-xl">
|
|
83
|
+
<q-item class="q-mb-md text-primary" clickable>
|
|
84
84
|
<q-item-section avatar>
|
|
85
85
|
<q-icon name="sym_r_chat" />
|
|
86
86
|
</q-item-section>
|
|
@@ -92,6 +92,8 @@
|
|
|
92
92
|
</div>
|
|
93
93
|
</q-item-label>
|
|
94
94
|
</q-item-section>
|
|
95
|
+
|
|
96
|
+
<pv-app-menu-help-chat :link="props.helpChatLink" :mini-brand="props.miniBrand" @update:model-value="setHasOpenedHelpChat" />
|
|
95
97
|
</q-item>
|
|
96
98
|
</q-list>
|
|
97
99
|
|
|
@@ -106,6 +108,7 @@
|
|
|
106
108
|
</template>
|
|
107
109
|
|
|
108
110
|
<script setup>
|
|
111
|
+
import PvAppMenuHelpChat from './private/PvAppMenuHelpChat.vue'
|
|
109
112
|
import PvAppMenuDropdown from './private/PvAppMenuDropdown.vue'
|
|
110
113
|
import QasAppUser from '../app-user/QasAppUser.vue'
|
|
111
114
|
|
|
@@ -113,12 +116,8 @@ import useAppMenuDropdown from './composables/use-app-menu-dropdown'
|
|
|
113
116
|
import useAppUser from './composables/use-app-user'
|
|
114
117
|
import useDevelopmentBadge from './composables/use-development-badge'
|
|
115
118
|
import { useScreen } from '../../composables'
|
|
116
|
-
import useAuthUser from '../../composables/private/use-auth-user'
|
|
117
|
-
|
|
118
|
-
import { handleProcess } from '../../helpers'
|
|
119
119
|
|
|
120
|
-
import
|
|
121
|
-
import { ref, computed, watch, onMounted } from 'vue'
|
|
120
|
+
import { ref, computed, watch } from 'vue'
|
|
122
121
|
import { useRouter } from 'vue-router'
|
|
123
122
|
|
|
124
123
|
defineOptions({
|
|
@@ -139,6 +138,11 @@ const props = defineProps({
|
|
|
139
138
|
type: String
|
|
140
139
|
},
|
|
141
140
|
|
|
141
|
+
helpChatLink: {
|
|
142
|
+
type: String,
|
|
143
|
+
default: ''
|
|
144
|
+
},
|
|
145
|
+
|
|
142
146
|
items: {
|
|
143
147
|
default: () => [],
|
|
144
148
|
type: Array
|
|
@@ -168,11 +172,6 @@ const props = defineProps({
|
|
|
168
172
|
title: {
|
|
169
173
|
default: '',
|
|
170
174
|
type: String
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
useChat: {
|
|
174
|
-
default: true,
|
|
175
|
-
type: Boolean
|
|
176
175
|
}
|
|
177
176
|
})
|
|
178
177
|
|
|
@@ -180,9 +179,6 @@ const emit = defineEmits(['sign-out', 'update:modelValue', 'toggle-notifications
|
|
|
180
179
|
|
|
181
180
|
const screen = useScreen()
|
|
182
181
|
const router = useRouter()
|
|
183
|
-
const { initializeChat, toggleChat } = useChatMenu()
|
|
184
|
-
|
|
185
|
-
onMounted(initializeChat)
|
|
186
182
|
|
|
187
183
|
const rootRoute = router.hasRoute('Root') ? { name: 'Root' } : { path: '/' }
|
|
188
184
|
|
|
@@ -220,7 +216,7 @@ const isMiniMode = computed(() => {
|
|
|
220
216
|
return screen.isLarge && isMini.value && !hasOpenedMenu.value && !hasOpenedHelpChat.value
|
|
221
217
|
})
|
|
222
218
|
|
|
223
|
-
const menuClasses = computed(() => ({ 'qas-app-menu__menu--spaced': !props.
|
|
219
|
+
const menuClasses = computed(() => ({ 'qas-app-menu__menu--spaced': !props.helpChatLink }))
|
|
224
220
|
|
|
225
221
|
const classes = computed(() => {
|
|
226
222
|
return {
|
|
@@ -305,53 +301,8 @@ function setHasOpenedMenu (value) {
|
|
|
305
301
|
hasOpenedMenu.value = value
|
|
306
302
|
}
|
|
307
303
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const { user } = useAuthUser()
|
|
311
|
-
|
|
312
|
-
const isMeVersionTwo = process.env.ME_VERSION === 2
|
|
313
|
-
|
|
314
|
-
function initializeChat () {
|
|
315
|
-
Gleap.initialize(handleProcess(() => process.env.GLEAP))
|
|
316
|
-
|
|
317
|
-
Gleap.setLanguage('pt-BR')
|
|
318
|
-
|
|
319
|
-
const {
|
|
320
|
-
uuid,
|
|
321
|
-
name,
|
|
322
|
-
email,
|
|
323
|
-
callingCode,
|
|
324
|
-
phone,
|
|
325
|
-
companyLink,
|
|
326
|
-
companyLinksOptions,
|
|
327
|
-
mainCompanyOptions, // somente na v2
|
|
328
|
-
currentMainCompany // somente na v2
|
|
329
|
-
} = user.value
|
|
330
|
-
|
|
331
|
-
const companyId = isMeVersionTwo ? currentMainCompany : companyLink
|
|
332
|
-
const companyNameList = isMeVersionTwo ? mainCompanyOptions : companyLinksOptions
|
|
333
|
-
|
|
334
|
-
const companyName = companyNameList?.find(({ value }) => value === companyId)?.label
|
|
335
|
-
|
|
336
|
-
Gleap.identify(uuid, {
|
|
337
|
-
name,
|
|
338
|
-
email,
|
|
339
|
-
phone: `+${callingCode || '55'}${phone}`,
|
|
340
|
-
companyId,
|
|
341
|
-
companyName
|
|
342
|
-
})
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function toggleChat () {
|
|
346
|
-
if (Gleap.isOpened()) return Gleap.close()
|
|
347
|
-
|
|
348
|
-
Gleap.open()
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return {
|
|
352
|
-
initializeChat,
|
|
353
|
-
toggleChat
|
|
354
|
-
}
|
|
304
|
+
function setHasOpenedHelpChat (value) {
|
|
305
|
+
hasOpenedHelpChat.value = value
|
|
355
306
|
}
|
|
356
307
|
</script>
|
|
357
308
|
|
|
@@ -15,6 +15,11 @@ props:
|
|
|
15
15
|
type: String
|
|
16
16
|
required: true
|
|
17
17
|
|
|
18
|
+
help-chat-link:
|
|
19
|
+
desc: Link para ser usado no iframe do chat, caso não passe nada, o chat não será exibido.
|
|
20
|
+
type: String
|
|
21
|
+
default: ''
|
|
22
|
+
|
|
18
23
|
items:
|
|
19
24
|
desc: Itens do menu.
|
|
20
25
|
type: Array
|
|
@@ -44,11 +49,6 @@ props:
|
|
|
44
49
|
desc: Título que vai ficar no label do select de módulos.
|
|
45
50
|
type: String
|
|
46
51
|
|
|
47
|
-
use-chat:
|
|
48
|
-
desc: Componente para controlar se vai ter ou não o chat.
|
|
49
|
-
type: Boolean
|
|
50
|
-
default: true
|
|
51
|
-
|
|
52
52
|
slots:
|
|
53
53
|
user:
|
|
54
54
|
desc: Slot para acessar o menu de usuário.
|
|
@@ -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>
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script setup>
|
|
8
|
+
import { baseProps } from '../../shared/badge-config'
|
|
9
|
+
|
|
8
10
|
import { computed } from 'vue'
|
|
9
11
|
|
|
10
12
|
defineOptions({
|
|
@@ -12,35 +14,7 @@ defineOptions({
|
|
|
12
14
|
inheritAttrs: false
|
|
13
15
|
})
|
|
14
16
|
|
|
15
|
-
const props = defineProps(
|
|
16
|
-
color: {
|
|
17
|
-
type: String,
|
|
18
|
-
default: 'light-blue-2'
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
label: {
|
|
22
|
-
type: String,
|
|
23
|
-
default: ''
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
multiLine: {
|
|
27
|
-
type: Boolean
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
textColor: {
|
|
31
|
-
type: String,
|
|
32
|
-
default: 'black'
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
removable: {
|
|
36
|
-
type: Boolean
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
tabindex: {
|
|
40
|
-
type: [String, Number],
|
|
41
|
-
default: undefined
|
|
42
|
-
}
|
|
43
|
-
})
|
|
17
|
+
const props = defineProps(baseProps)
|
|
44
18
|
|
|
45
19
|
const emit = defineEmits(['remove'])
|
|
46
20
|
const model = defineModel({ type: Boolean, default: true })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div class="qas-checkbox">
|
|
3
3
|
<!-- Single -->
|
|
4
4
|
<q-checkbox v-if="isSingle" v-model="model" v-bind="singleAttributes" dense>
|
|
5
5
|
<slot />
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
<!-- Group -->
|
|
9
9
|
<div v-else>
|
|
10
|
-
<div v-if="hasCheckboxLabel" class="q-mb-sm text-body1">
|
|
11
|
-
{{
|
|
10
|
+
<div v-if="hasCheckboxLabel" class="q-mb-sm text-body1" :class="checkboxLabelClasses">
|
|
11
|
+
{{ formattedLabel }}
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
14
|
<div :class="classes">
|
|
15
15
|
<div v-for="(option, index) in props.options" :key="index">
|
|
16
16
|
<!-- Com children -->
|
|
17
|
-
<q-checkbox v-if="hasChildren(option)" :class="getCheckboxClass(option)" dense :label="option.label" :model-value="getModelValue(index)" @update:model-value="updateCheckbox($event, option, index)" />
|
|
17
|
+
<q-checkbox v-if="hasChildren(option)" :class="getCheckboxClass(option)" dense :indeterminate-value="false" :label="option.label" :model-value="getModelValue(index)" @update:model-value="updateCheckbox($event, option, index)" />
|
|
18
18
|
|
|
19
19
|
<!-- Com children -->
|
|
20
20
|
<q-option-group v-if="hasChildren(option)" class="q-ml-xs q-mt-xs" dense :inline="props.inline" :model-value="props.modelValue" :options="option.children" type="checkbox" @update:model-value="updateChildren($event, option, index)" />
|
|
@@ -24,10 +24,15 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
|
+
|
|
28
|
+
<div v-if="props.errorMessage" class="q-pt-sm qas-checkbox__error-message text-negative">
|
|
29
|
+
{{ props.errorMessage }}
|
|
30
|
+
</div>
|
|
27
31
|
</div>
|
|
28
32
|
</template>
|
|
29
33
|
|
|
30
34
|
<script setup>
|
|
35
|
+
import { getRequiredLabel } from '../../helpers'
|
|
31
36
|
import { watch, computed, ref, onMounted, useAttrs } from 'vue'
|
|
32
37
|
|
|
33
38
|
defineOptions({
|
|
@@ -54,6 +59,19 @@ const props = defineProps({
|
|
|
54
59
|
inline: {
|
|
55
60
|
default: true,
|
|
56
61
|
type: Boolean
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
errorMessage: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: ''
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
error: {
|
|
70
|
+
type: Boolean
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
required: {
|
|
74
|
+
type: Boolean
|
|
57
75
|
}
|
|
58
76
|
})
|
|
59
77
|
|
|
@@ -92,6 +110,14 @@ const singleAttributes = computed(() => {
|
|
|
92
110
|
}
|
|
93
111
|
})
|
|
94
112
|
|
|
113
|
+
const checkboxLabelClasses = computed(() => {
|
|
114
|
+
return { 'text-negative': props.error }
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const formattedLabel = computed(() => {
|
|
118
|
+
return getRequiredLabel({ label: props.label, required: props.required })
|
|
119
|
+
})
|
|
120
|
+
|
|
95
121
|
// watch
|
|
96
122
|
watch(() => props.options, handleParent)
|
|
97
123
|
|
|
@@ -116,7 +142,7 @@ function setGroupIntersection (value, option, index) {
|
|
|
116
142
|
const options = option.children.map(item => item.value)
|
|
117
143
|
const intersection = options.filter(item => value.includes(item))
|
|
118
144
|
|
|
119
|
-
group.value[index] = intersection.length
|
|
145
|
+
group.value[index] = intersection.length ? (intersection.length === options.length ? true : null) : false
|
|
120
146
|
}
|
|
121
147
|
|
|
122
148
|
function updateCheckbox (value, option, index) {
|
|
@@ -148,3 +174,12 @@ function getModelValue (index) {
|
|
|
148
174
|
return group.value[index]
|
|
149
175
|
}
|
|
150
176
|
</script>
|
|
177
|
+
|
|
178
|
+
<style lang="scss">
|
|
179
|
+
.qas-checkbox {
|
|
180
|
+
&__error-message {
|
|
181
|
+
// Tamanho da fonte utilizada nos errors no q-field.
|
|
182
|
+
font-size: 11px;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
</style>
|
|
@@ -26,6 +26,19 @@ props:
|
|
|
26
26
|
default: []
|
|
27
27
|
type: Array
|
|
28
28
|
|
|
29
|
+
error:
|
|
30
|
+
desc: Booleano que caso seja true a label passa a ter a cor vermelha.
|
|
31
|
+
type: Boolean
|
|
32
|
+
|
|
33
|
+
error-message:
|
|
34
|
+
desc: Mensagem de erro exibida na parte inferior do checkbox.
|
|
35
|
+
type: String
|
|
36
|
+
|
|
37
|
+
required:
|
|
38
|
+
desc: Controla se exibirá o sufixo "*" ao lado do texto.
|
|
39
|
+
default: false
|
|
40
|
+
type: Boolean
|
|
41
|
+
|
|
29
42
|
events:
|
|
30
43
|
'@update:model-value -> function(value)':
|
|
31
44
|
desc: Dispara quando o model-value altera, também usado para v-model.
|
|
@@ -146,7 +146,7 @@ export default {
|
|
|
146
146
|
time: { ...datetimeInput, useTimeOnly: true },
|
|
147
147
|
|
|
148
148
|
boolean: { is: 'qas-toggle', label, ...error },
|
|
149
|
-
checkbox: { is: 'qas-checkbox', label, options, ...error },
|
|
149
|
+
checkbox: { is: 'qas-checkbox', label, options, ...error, required },
|
|
150
150
|
radio: { is: 'qas-radio', label, options },
|
|
151
151
|
|
|
152
152
|
upload: { is: 'qas-uploader', accept, autoUpload: true, entity, label, multiple, readonly, maxFiles, ...error },
|
|
@@ -24,7 +24,7 @@ props:
|
|
|
24
24
|
default: {}
|
|
25
25
|
type: Object
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
errors:
|
|
28
28
|
desc: Objeto contendo propriedades contendo a mensagem de erro.
|
|
29
29
|
default: {}
|
|
30
30
|
type: Object
|
|
@@ -55,7 +55,7 @@ props:
|
|
|
55
55
|
|
|
56
56
|
gutter:
|
|
57
57
|
desc: Espaçamento entre colunas.
|
|
58
|
-
default:
|
|
58
|
+
default: md
|
|
59
59
|
type: [String, Boolean]
|
|
60
60
|
examples: [xs, sm, md, lg, xl, '2xl', '3xl', '4xl', '5xl', false]
|
|
61
61
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<qas-header v-if="hasHeader" v-bind="props.headerProps" />
|
|
4
4
|
|
|
5
5
|
<div :class="classes">
|
|
6
|
-
<div v-for="(field, key) in fieldsByResult" :key="key" :class="
|
|
6
|
+
<div v-for="(field, key) in fieldsByResult" :key="key" :class="getContainerClasses({ key })">
|
|
7
7
|
<slot :field="field" :name="`field-${field.name}`">
|
|
8
8
|
<qas-grid-item :use-ellipsis="hasEllipsis(field)" :use-inline="props.useInline">
|
|
9
9
|
<template #header>
|
|
@@ -98,7 +98,7 @@ const props = defineProps({
|
|
|
98
98
|
})
|
|
99
99
|
|
|
100
100
|
// composables
|
|
101
|
-
const { classes, getFieldClass } = useGenerator({ props })
|
|
101
|
+
const { classes, getFieldClass } = useGenerator({ props, isGrid: true })
|
|
102
102
|
|
|
103
103
|
// computed
|
|
104
104
|
const hasResult = computed(() => Object.keys(props.result).length)
|
|
@@ -181,7 +181,7 @@ function setFieldsByResult () {
|
|
|
181
181
|
fieldsByResult.value = getFieldsByResult()
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
function
|
|
184
|
+
function getContainerClasses ({ key }) {
|
|
185
185
|
if (props.useInline) return 'row justify-between col-12'
|
|
186
186
|
|
|
187
187
|
return getFieldClass({ index: key, isGridGenerator: true })
|
|
@@ -36,8 +36,8 @@ props:
|
|
|
36
36
|
examples: ["{ email: { name: 'email', type: 'email', label: 'E-mail' } }"]
|
|
37
37
|
|
|
38
38
|
gutter:
|
|
39
|
-
desc:
|
|
40
|
-
default:
|
|
39
|
+
desc: Em caso de uso inline o default é "sm" senão é "md".
|
|
40
|
+
default: ''
|
|
41
41
|
type: String
|
|
42
42
|
examples: [xs, sm, md, lg, xl, 2xl, 3xl, 4xl, 5xl]
|
|
43
43
|
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
<div ref="parent" :class="classes">
|
|
3
3
|
<div class="no-wrap row text-no-wrap">
|
|
4
4
|
<div ref="truncate" class="ellipsis">
|
|
5
|
-
<slot>
|
|
5
|
+
<slot>
|
|
6
|
+
<div v-if="hasBadges" class="items-center q-col-gutter-sm row" :class="badgeParentClasses">
|
|
7
|
+
<div v-for="(item, index) in normalizedBadgesList" :key="index">
|
|
8
|
+
<qas-badge v-bind="getBadgeProps(item)" />
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div v-else class="ellipsis">
|
|
13
|
+
{{ formattedText }}
|
|
14
|
+
</div>
|
|
15
|
+
</slot>
|
|
6
16
|
</div>
|
|
7
17
|
|
|
8
18
|
<qas-btn v-if="hasButton" class="q-ml-sm" :label="buttonLabel" @click.stop.prevent="toggle" />
|
|
@@ -10,7 +20,7 @@
|
|
|
10
20
|
|
|
11
21
|
<qas-dialog v-model="show" v-bind="defaultProps" aria-label="Diálogo de texto completo" role="dialog">
|
|
12
22
|
<template v-if="isCounterMode" #description>
|
|
13
|
-
<div class="q-col-gutter-y-
|
|
23
|
+
<div class="q-col-gutter-y-sm row">
|
|
14
24
|
<div
|
|
15
25
|
v-for="(item, index) in normalizedList"
|
|
16
26
|
:key="index"
|
|
@@ -25,6 +35,10 @@
|
|
|
25
35
|
</template>
|
|
26
36
|
|
|
27
37
|
<script setup>
|
|
38
|
+
import QasDialog from '../dialog/QasDialog.vue'
|
|
39
|
+
|
|
40
|
+
import { baseProps } from '../../shared/badge-config'
|
|
41
|
+
|
|
28
42
|
import {
|
|
29
43
|
computed,
|
|
30
44
|
onMounted,
|
|
@@ -33,9 +47,6 @@ import {
|
|
|
33
47
|
watch
|
|
34
48
|
} from 'vue'
|
|
35
49
|
|
|
36
|
-
import QasDialog from '../dialog/QasDialog.vue'
|
|
37
|
-
|
|
38
|
-
// define component name
|
|
39
50
|
defineOptions({ name: 'QasTextTruncate' })
|
|
40
51
|
|
|
41
52
|
// props
|
|
@@ -55,6 +66,11 @@ const props = defineProps({
|
|
|
55
66
|
default: ''
|
|
56
67
|
},
|
|
57
68
|
|
|
69
|
+
emptyText: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: '-'
|
|
72
|
+
},
|
|
73
|
+
|
|
58
74
|
maxWidth: {
|
|
59
75
|
type: Number,
|
|
60
76
|
default: 0
|
|
@@ -85,13 +101,16 @@ const props = defineProps({
|
|
|
85
101
|
default: () => []
|
|
86
102
|
},
|
|
87
103
|
|
|
104
|
+
useBadge: {
|
|
105
|
+
type: Boolean
|
|
106
|
+
},
|
|
107
|
+
|
|
88
108
|
useObjectList: {
|
|
89
109
|
type: Boolean
|
|
90
110
|
},
|
|
91
111
|
|
|
92
|
-
|
|
93
|
-
type:
|
|
94
|
-
default: '-'
|
|
112
|
+
useWrapBadge: {
|
|
113
|
+
type: Boolean
|
|
95
114
|
}
|
|
96
115
|
})
|
|
97
116
|
|
|
@@ -100,11 +119,18 @@ const truncate = ref(null)
|
|
|
100
119
|
const parent = ref(null)
|
|
101
120
|
|
|
102
121
|
// composable
|
|
122
|
+
const {
|
|
123
|
+
hasBadges,
|
|
124
|
+
badgeParentClasses,
|
|
125
|
+
normalizedBadgesList,
|
|
126
|
+
getBadgeProps
|
|
127
|
+
} = useBadgeHandler()
|
|
128
|
+
|
|
103
129
|
const {
|
|
104
130
|
textContent,
|
|
105
131
|
isTruncated,
|
|
106
132
|
truncateText
|
|
107
|
-
} = useTruncate({ parent, props })
|
|
133
|
+
} = useTruncate({ parent, props, hasBadges })
|
|
108
134
|
|
|
109
135
|
const {
|
|
110
136
|
defaultProps,
|
|
@@ -122,6 +148,7 @@ const {
|
|
|
122
148
|
|
|
123
149
|
useMutationObserver({ truncate, callbackFn: truncateText })
|
|
124
150
|
|
|
151
|
+
// computeds
|
|
125
152
|
const classes = computed(() => [`text-${props.color}`, `text-${props.typography}`])
|
|
126
153
|
|
|
127
154
|
const formattedText = computed(() => props.list.length || props.text ? displayText.value : props.emptyText)
|
|
@@ -182,7 +209,7 @@ function useMutationObserver ({ truncate, callbackFn = () => {} }) {
|
|
|
182
209
|
}
|
|
183
210
|
}
|
|
184
211
|
|
|
185
|
-
function useTruncate ({ parent, props }) {
|
|
212
|
+
function useTruncate ({ parent, props, hasBadges }) {
|
|
186
213
|
// reactive vars
|
|
187
214
|
const maxPossibleWidth = ref('')
|
|
188
215
|
const textContent = ref('')
|
|
@@ -191,14 +218,17 @@ function useTruncate ({ parent, props }) {
|
|
|
191
218
|
// lifecycle
|
|
192
219
|
onMounted(() => truncateText())
|
|
193
220
|
|
|
194
|
-
// watch
|
|
195
|
-
watch(() => props.maxWidth, truncateText)
|
|
196
|
-
|
|
197
221
|
// computed
|
|
198
222
|
const isTruncated = computed(() => textWidth.value > maxPossibleWidth.value)
|
|
199
223
|
|
|
224
|
+
// watch
|
|
225
|
+
watch(() => props.maxWidth, truncateText)
|
|
226
|
+
|
|
200
227
|
// functions
|
|
201
228
|
function truncateText () {
|
|
229
|
+
// Se tiver badges, então não pode ser feito calculo de width.
|
|
230
|
+
if (hasBadges.value) return
|
|
231
|
+
|
|
202
232
|
parent.value.style.maxWidth = '100%'
|
|
203
233
|
textWidth.value = truncate.value.clientWidth
|
|
204
234
|
textContent.value = truncate.value?.innerHTML
|
|
@@ -271,4 +301,34 @@ function useCounter () {
|
|
|
271
301
|
counterLabel
|
|
272
302
|
}
|
|
273
303
|
}
|
|
304
|
+
|
|
305
|
+
function useBadgeHandler () {
|
|
306
|
+
const hasBadges = computed(() => props.useBadge && props.useObjectList && props.list.length)
|
|
307
|
+
|
|
308
|
+
const normalizedBadgesList = computed(() => props.list.slice(0, props.maxVisibleItem))
|
|
309
|
+
const badgeParentClasses = computed(() => ({ 'no-wrap': !props.useWrapBadge }))
|
|
310
|
+
|
|
311
|
+
function getBadgeProps (item) {
|
|
312
|
+
const itemProps = {}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* recupera somente keys que estão em baseProps do QasBadge
|
|
316
|
+
* pra evitar que passe propriedades desnecessárias
|
|
317
|
+
*/
|
|
318
|
+
for (const key in item) {
|
|
319
|
+
if (baseProps[key]) {
|
|
320
|
+
itemProps[key] = item[key]
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return itemProps
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
hasBadges,
|
|
329
|
+
badgeParentClasses,
|
|
330
|
+
normalizedBadgesList,
|
|
331
|
+
getBadgeProps
|
|
332
|
+
}
|
|
333
|
+
}
|
|
274
334
|
</script>
|
|
@@ -18,6 +18,10 @@ props:
|
|
|
18
18
|
desc: Seta o título do dialog.
|
|
19
19
|
type: String
|
|
20
20
|
|
|
21
|
+
empty-text:
|
|
22
|
+
desc: Texto a ser exibido no caso de não houver itens no "list" ou o "text" for vazio.
|
|
23
|
+
type: String
|
|
24
|
+
|
|
21
25
|
max-width:
|
|
22
26
|
desc: Seta o tamanho máximo do texto.
|
|
23
27
|
type: Number
|
|
@@ -47,13 +51,20 @@ props:
|
|
|
47
51
|
default: []
|
|
48
52
|
type: Array
|
|
49
53
|
|
|
54
|
+
use-badge:
|
|
55
|
+
desc: Habilita badges para cada item da lista.
|
|
56
|
+
default: false
|
|
57
|
+
type: Boolean
|
|
58
|
+
|
|
50
59
|
use-object-list:
|
|
51
60
|
desc: Utiliza a propriedade "list" como array de objeto contendo label (uso junto a prop list).
|
|
61
|
+
default: false
|
|
52
62
|
type: Boolean
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
desc:
|
|
56
|
-
|
|
64
|
+
use-wrap-badge:
|
|
65
|
+
desc: habilita a quebra de linha das badges quando o texto for muito grande.
|
|
66
|
+
default: false
|
|
67
|
+
type: Boolean
|
|
57
68
|
|
|
58
69
|
slots:
|
|
59
70
|
default:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { computed } from 'vue'
|
|
2
1
|
import { Spacing } from '../../enums/Spacing'
|
|
3
2
|
import { gutterValidator } from '../../helpers/private/gutter-validator'
|
|
3
|
+
import useScreen from '../use-screen'
|
|
4
|
+
|
|
5
|
+
import { computed } from 'vue'
|
|
4
6
|
|
|
5
7
|
const IRREGULAR_CLASSES = ['col', 'col-auto', 'fit']
|
|
6
8
|
|
|
@@ -21,7 +23,7 @@ export const baseProps = {
|
|
|
21
23
|
},
|
|
22
24
|
|
|
23
25
|
gutter: {
|
|
24
|
-
default:
|
|
26
|
+
default: undefined,
|
|
25
27
|
type: [String, Boolean],
|
|
26
28
|
validator: gutterValidator
|
|
27
29
|
}
|
|
@@ -34,20 +36,33 @@ export const baseProps = {
|
|
|
34
36
|
* @name useGenerator
|
|
35
37
|
* @param {Object} options - Opções do componente.
|
|
36
38
|
* @param {baseProps} options.props - Propriedades do componente.
|
|
39
|
+
* @param {boolean} options.isGrid - Propriedades do componente.
|
|
37
40
|
* @returns {{
|
|
38
41
|
* classes: classes,
|
|
39
42
|
* getFieldClass: getFieldClass
|
|
40
43
|
* }}
|
|
41
44
|
*/
|
|
42
|
-
export default function ({ props = {} }) {
|
|
45
|
+
export default function ({ props = {}, isGrid = false }) {
|
|
46
|
+
const screen = useScreen()
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Se a propriedade gutter não for passada, será calculada automaticamente.
|
|
50
|
+
* se for usado no grid e for inline, o gutter será menor.
|
|
51
|
+
*/
|
|
52
|
+
const defaultGutter = computed(() => {
|
|
53
|
+
if (props.gutter !== undefined) return props.gutter
|
|
54
|
+
|
|
55
|
+
return isGrid && (props.useInline && !screen.isSmall) ? Spacing.Sm : Spacing.Md
|
|
56
|
+
})
|
|
57
|
+
|
|
43
58
|
/**
|
|
44
59
|
* @type {{ value: string[] | string }}
|
|
45
60
|
*/
|
|
46
61
|
const classes = computed(() => {
|
|
47
62
|
const classesList = ['row']
|
|
48
63
|
|
|
49
|
-
if (
|
|
50
|
-
classesList.push(`q-col-gutter-${
|
|
64
|
+
if (defaultGutter.value) {
|
|
65
|
+
classesList.push(`q-col-gutter-${defaultGutter.value}`)
|
|
51
66
|
}
|
|
52
67
|
|
|
53
68
|
return classesList
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const baseProps = {
|
|
2
|
+
color: {
|
|
3
|
+
type: String,
|
|
4
|
+
default: 'light-blue-2'
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
label: {
|
|
8
|
+
type: String,
|
|
9
|
+
default: ''
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
multiLine: {
|
|
13
|
+
type: Boolean
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
textColor: {
|
|
17
|
+
type: String,
|
|
18
|
+
default: 'black'
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
removable: {
|
|
22
|
+
type: Boolean
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
tabindex: {
|
|
26
|
+
type: [String, Number],
|
|
27
|
+
default: undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { LocalStorage } from 'quasar'
|
|
2
|
-
|
|
3
|
-
import { ref } from 'vue'
|
|
4
|
-
|
|
5
|
-
const user = ref(LocalStorage.getItem('user'))
|
|
6
|
-
|
|
7
|
-
export default function useAuthUser () {
|
|
8
|
-
window.addEventListener('message', ({ data }) => {
|
|
9
|
-
if (data.type !== 'updateUser') return
|
|
10
|
-
|
|
11
|
-
user.value = data.user
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
user
|
|
16
|
-
}
|
|
17
|
-
}
|