@bildvitta/quasar-ui-asteroid 3.16.0-beta.0 → 3.16.0-beta.2

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.16.0-beta.0",
4
+ "version": "3.16.0-beta.2",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -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
- <!-- User -->
79
- <div v-if="showAppUser" class="full-width q-pb-lg q-px-lg">
80
- <qas-app-user v-bind="defaultAppUserProps" />
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
  }
@@ -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
@@ -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>
@@ -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.
@@ -10,6 +10,10 @@ props:
10
10
  type: [Array, String, Object]
11
11
  examples: ["{ name: { sm: 6, md: 12 } }", "[{ sm: 6, md: 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
  content-class:
14
18
  desc: Classe de cada "div" pai referente ao resultado.
15
19
  default: ''
@@ -57,9 +61,6 @@ props:
57
61
  desc: Adiciona a disposição dos campos por linha, ou seja, header e content ocupando a linha toda.
58
62
  type: Boolean
59
63
 
60
- use-common-columns:
61
- desc: Usado quando precisa passar a prop "columns" como objeto sendo que será o valor comum para todos fields
62
-
63
64
  slots:
64
65
  content:
65
66
  desc: Slot para o conteúdo (content).
@@ -10,6 +10,11 @@ export const baseProps = {
10
10
  type: [Array, String, Object]
11
11
  },
12
12
 
13
+ commonColumns: {
14
+ default: () => ({}),
15
+ type: [Object, String]
16
+ },
17
+
13
18
  fields: {
14
19
  default: () => ({}),
15
20
  type: Object
@@ -19,10 +24,6 @@ export const baseProps = {
19
24
  default: Spacing.Lg,
20
25
  type: [String, Boolean],
21
26
  validator: gutterValidator
22
- },
23
-
24
- useCommonColumns: {
25
- type: Boolean
26
27
  }
27
28
  }
28
29
 
@@ -61,9 +62,7 @@ export default function ({ props = {} }) {
61
62
  * @returns {(string|string[])}
62
63
  */
63
64
  function getFieldClass ({ index, isGridGenerator, fields }) {
64
- if (typeof props.columns === 'string') {
65
- return IRREGULAR_CLASSES.includes(props.columns) ? props.columns : `col-${props.columns}`
66
- }
65
+ if (typeof props.columns === 'string') return _getStringColumns(props.columns)
67
66
 
68
67
  return Array.isArray(props.columns)
69
68
  ? _handleColumnsByIndex({ index, isGridGenerator, fields })
@@ -72,7 +71,13 @@ export default function ({ props = {} }) {
72
71
 
73
72
  /**
74
73
  * @private
75
- */
74
+ */
75
+ function _getStringColumns (columns) {
76
+ return IRREGULAR_CLASSES.includes(columns) ? columns : `col-${columns}`
77
+ }
78
+ /**
79
+ * @private
80
+ */
76
81
  function _getBreakpoint (columns) {
77
82
  const classes = []
78
83
 
@@ -104,6 +109,10 @@ export default function ({ props = {} }) {
104
109
  * @private
105
110
  */
106
111
  function _getDefaultColumnClass (isGridGenerator) {
112
+ if (typeof props.commonColumns === 'string') return _getStringColumns(props.commonColumns)
113
+
114
+ if (Object.keys(props.commonColumns).length) return _getBreakpoint(props.commonColumns)
115
+
107
116
  return isGridGenerator ? 'col-6 col-xs-12 col-sm-4' : 'col-6'
108
117
  }
109
118
 
@@ -111,13 +120,6 @@ export default function ({ props = {} }) {
111
120
  * @private
112
121
  */
113
122
  function _handleColumnsByField ({ index, isGridGenerator }) {
114
- /*
115
- * Quando é passado o columns como um único objeto que será replicado para todos fields.
116
- */
117
- if (props.useCommonColumns && Object.keys(props.columns).length) {
118
- return _getBreakpoint(props.columns)
119
- }
120
-
121
123
  /*
122
124
  * Quando não é passado columns, retornará o default.
123
125
  */