@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bildvitta/quasar-ui-asteroid",
3
3
  "description": "Asteroid",
4
- "version": "3.17.0-beta.27-alpha.0",
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="useChat" class="q-mt-xl">
83
- <q-item class="q-mb-md text-primary" clickable @click="toggleChat">
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 Gleap from 'gleap'
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.useChat }))
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
- // composables definition
309
- function useChatMenu () {
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
- {{ props.label }}
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 && (intersection.length === options.length ? true : null)
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
- error:
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: lg
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="getContainerClassses({ key })">
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 getContainerClassses ({ key }) {
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: Espaçamento entre colunas.
40
- default: lg
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
 
@@ -65,8 +65,8 @@ const classes = computed(() => {
65
65
  },
66
66
 
67
67
  content: {
68
+ 'text-grey-10': true,
68
69
  'text-body1': !isInline,
69
- 'text-grey-10': !isInline,
70
70
  'text-subtitle1': isInline,
71
71
  ellipsis: hasEllipsis.value
72
72
  }
@@ -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>{{ formattedText }}</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-md row">
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
- emptyText: {
93
- type: String,
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
- empty-text:
55
- desc: Texto a ser exibido no caso de não houver itens no "list" ou o "text" for vazio.
56
- type: String
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: Spacing.Md,
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 (props.gutter) {
50
- classesList.push(`q-col-gutter-${props.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
- }