@bildvitta/quasar-ui-asteroid 3.5.0-beta.0 → 3.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +2 -2
  2. package/src/components/actions-menu/QasActionsMenu.vue +86 -37
  3. package/src/components/actions-menu/QasActionsMenu.yml +6 -6
  4. package/src/components/app-menu/QasAppMenu.vue +1 -1
  5. package/src/components/copy/QasCopy.vue +1 -1
  6. package/src/components/delete/QasDelete.vue +1 -1
  7. package/src/components/field/QasField.vue +7 -0
  8. package/src/components/form-generator/QasFormGenerator.yml +1 -1
  9. package/src/components/grid-generator/QasGridGenerator.yml +1 -1
  10. package/src/components/list-view/QasListView.vue +67 -8
  11. package/src/components/list-view/QasListView.yml +4 -0
  12. package/src/components/nested-fields/QasNestedFields.vue +101 -41
  13. package/src/components/nested-fields/QasNestedFields.yml +30 -10
  14. package/src/components/pagination/QasPagination.vue +27 -0
  15. package/src/components/pagination/QasPagination.yml +4 -0
  16. package/src/components/table-generator/QasTableGenerator.vue +10 -2
  17. package/src/components/table-generator/QasTableGenerator.yml +14 -0
  18. package/src/components/text-truncate/QasTextTruncate.vue +1 -1
  19. package/src/css/components/item.scss +4 -0
  20. package/src/css/plugins/index.scss +1 -0
  21. package/src/css/plugins/notify.scss +40 -0
  22. package/src/css/variables/spacing.scss +5 -5
  23. package/src/helpers/set-scroll-on-grab.js +13 -4
  24. package/src/index.scss +4 -1
  25. package/src/mixins/generator.js +1 -1
  26. package/src/plugins/notify-error/NotifyError.js +7 -5
  27. package/src/plugins/notify-success/NotifySuccess.js +7 -5
  28. package/src/shared/notify-config.js +7 -0
  29. package/src/vue-plugin.js +3 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bildvitta/quasar-ui-asteroid",
3
3
  "description": "Asteroid",
4
- "version": "3.5.0-beta.0",
4
+ "version": "3.5.0-beta.1",
5
5
  "author": "Bild & Vitta <systemteam@bild.com.br>",
6
6
  "license": "MIT",
7
7
  "main": "dist/asteroid.cjs.min.js",
@@ -64,4 +64,4 @@
64
64
  "tags": "dist/vetur/asteroid-tags.json",
65
65
  "attributes": "dist/vetur/asteroid-attributes.json"
66
66
  }
67
- }
67
+ }
@@ -1,49 +1,45 @@
1
1
  <template>
2
- <qas-btn class="qas-actions-menu" color="primary" :icon="icon" :label="label" outline padding="md" :use-label-on-small-screen="false">
3
- <q-menu class="qas-actions-menu__menu">
4
- <q-list class="qas-actions-menu__list" separator>
5
- <slot v-for="(item, key) in list" :item="item" :name="key">
6
- <q-item :key="key" class="text-primary" clickable v-bind="item.props" @click="onClick(item)">
7
- <q-item-section>
8
- <div class="flex items-center justify-center q-gutter-x-md">
9
- <q-icon :name="item.icon" size="sm" />
2
+ <div v-if="hasActions">
3
+ <component :is="component.is" flat v-bind="component.props" :use-label-on-small-screen="useLabelOnSmallScreen" @click="onClick()">
4
+ <q-menu v-if="hasMoreThanOneAction" auto-close class="q-py-xs">
5
+ <q-list>
6
+ <slot v-for="(item, key) in actions" :item="item" :name="key">
7
+ <component :is="getComponent(key)" v-bind="item.props" :key="key" clickable @click="onClick(item)">
8
+ <q-item-section avatar>
9
+ <q-icon :name="item.icon" />
10
+ </q-item-section>
11
+
12
+ <q-item-section>
10
13
  <div>{{ item.label }}</div>
11
- </div>
12
- </q-item-section>
13
- </q-item>
14
- </slot>
15
-
16
- <qas-delete v-if="hasDelete" v-bind="deleteProps" class="text-negative" clickable tag="q-item">
17
- <q-item-section>
18
- <div class="flex items-center justify-center q-gutter-x-sm">
19
- <q-icon :name="deleteIcon" size="sm" />
20
- <div>{{ deleteLabel }}</div>
21
- </div>
22
- </q-item-section>
23
- </qas-delete>
24
- </q-list>
25
- </q-menu>
26
- </qas-btn>
14
+ </q-item-section>
15
+ </component>
16
+ </slot>
17
+ </q-list>
18
+ </q-menu>
19
+ </component>
20
+ </div>
27
21
  </template>
28
22
 
29
23
  <script>
30
24
  import QasBtn from '../btn/QasBtn.vue'
25
+ import QasDelete from '../delete/QasDelete.vue'
31
26
 
32
27
  export default {
33
28
  name: 'QasActionsMenu',
34
29
 
35
30
  components: {
36
- QasBtn
31
+ QasBtn,
32
+ QasDelete
37
33
  },
38
34
 
39
35
  props: {
40
36
  icon: {
41
- default: 'o_settings',
37
+ default: 'o_more_vert',
42
38
  type: String
43
39
  },
44
40
 
45
41
  label: {
46
- default: 'Configurações',
42
+ default: 'Opções',
47
43
  type: String
48
44
  },
49
45
 
@@ -65,17 +61,79 @@ export default {
65
61
  deleteProps: {
66
62
  default: () => ({}),
67
63
  type: Object
64
+ },
65
+
66
+ useLabelOnSmallScreen: {
67
+ default: true,
68
+ type: Boolean
68
69
  }
69
70
  },
70
71
 
71
72
  computed: {
73
+ actions () {
74
+ return {
75
+ ...this.list,
76
+ ...(this.hasDelete && {
77
+ delete: {
78
+ icon: this.deleteIcon,
79
+ label: this.deleteLabel,
80
+ props: {
81
+ ...this.deleteProps,
82
+ tag: this.hasMoreThanOneAction ? 'q-item' : 'qas-btn'
83
+ }
84
+ }
85
+ })
86
+ }
87
+ },
88
+
89
+ component () {
90
+ const props = {}
91
+
92
+ if (this.hasMoreThanOneAction) {
93
+ props.label = 'Opções'
94
+ props.iconRight = this.icon
95
+ props.textColor = 'dark'
96
+ } else {
97
+ props.icon = this.actions[this.firstItemKey]?.icon
98
+ props.label = this.actions[this.firstItemKey]?.label
99
+ props.color = 'primary'
100
+ }
101
+
102
+ this.hasDelete && Object.assign(props, this.deleteProps)
103
+
104
+ return {
105
+ is: this.hasMoreThanOneAction || !this.hasDelete ? 'qas-btn' : 'qas-delete',
106
+ props
107
+ }
108
+ },
109
+
110
+ firstItemKey () {
111
+ return Object.keys(this.actions)?.[0]
112
+ },
113
+
114
+ hasActions () {
115
+ return !!Object.keys(this.actions).length
116
+ },
117
+
72
118
  hasDelete () {
73
119
  return !!Object.keys(this.deleteProps).length
120
+ },
121
+
122
+ hasMoreThanOneAction () {
123
+ return Object.keys(this.actions || {}).length > 1
74
124
  }
75
125
  },
76
126
 
77
127
  methods: {
78
- onClick (item) {
128
+ getComponent (key) {
129
+ return key === 'delete' ? 'qas-delete' : 'q-item'
130
+ },
131
+
132
+ onClick (item = {}) {
133
+ if (!this.hasMoreThanOneAction) {
134
+ item = this.actions[this.firstItemKey]
135
+ }
136
+
79
137
  if (typeof item.handler === 'function') {
80
138
  const { handler, ...filtered } = item
81
139
  item.handler(filtered)
@@ -84,12 +142,3 @@ export default {
84
142
  }
85
143
  }
86
144
  </script>
87
-
88
- <style lang="scss">
89
- .qas-actions-menu {
90
- &__list {
91
- width: 265px;
92
- z-index: 1;
93
- }
94
- }
95
- </style>
@@ -21,15 +21,10 @@ props:
21
21
 
22
22
  icon:
23
23
  desc: Ícone do botão.
24
- default: o_settings
24
+ default: o_more_vert
25
25
  type: String
26
26
  examples: [start, end, between, around, center]
27
27
 
28
- label:
29
- desc: Rotulo do botão.
30
- default: Configurações
31
- type: String
32
-
33
28
  list:
34
29
  desc: Lista de items que vão ser criados dentro do menu de ações.
35
30
  default: '{}'
@@ -44,6 +39,11 @@ props:
44
39
  }"
45
40
  ]
46
41
 
42
+ use-label-on-small-screen:
43
+ desc: Esconde o rótulo (label) do botão quando o tamanho da tela for pequeno (esta propriedade só funciona se o "rotulo") for passado via propriedade "label".
44
+ default: true
45
+ type: Boolean
46
+
47
47
  slots:
48
48
  '[nome-da-chave]':
49
49
  desc: 'Slot dinâmico gerado a partir das chaves dentro do objeto da prop "list"'
@@ -179,7 +179,7 @@ export default {
179
179
  }
180
180
 
181
181
  &__children.q-item {
182
- padding-left: var(--qas-spacing-lg);
182
+ padding-left: var(--qas-spacing-xl);
183
183
  }
184
184
 
185
185
  &__item-children.q-item + &__item-children.q-item {
@@ -2,7 +2,7 @@
2
2
  <span>
3
3
  <slot>{{ text }}</slot>
4
4
 
5
- <qas-btn class="q-ml-xs" color="grey-7" flat :icon="icon" :loading="isLoading" round :size="size" @click="copy">
5
+ <qas-btn class="q-ml-xs" color="grey-7" flat :icon="icon" :loading="isLoading" round :size="size" @click.stop="copy">
6
6
  <q-tooltip>Copiar</q-tooltip>
7
7
  </qas-btn>
8
8
  </span>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <component v-bind="$attrs" :is="tag" @click="openConfirmDialog">
2
+ <component v-bind="$attrs" :is="tag" @click.stop="openConfirmDialog">
3
3
  <template v-for="(_, name) in $slots" #[name]="context">
4
4
  <slot :name="name" v-bind="context || {}" />
5
5
  </template>
@@ -159,11 +159,18 @@ export default {
159
159
  // This computed will change the key name when the server sends different key.
160
160
  formattedField () {
161
161
  const field = {}
162
+ const nonRequiredFieldsLabel = ['boolean', 'checkbox', 'radio']
162
163
 
163
164
  for (const key in this.field) {
164
165
  field[attributesProfile[key] || key] = this.field[key]
165
166
  }
166
167
 
168
+ const { label, required, type } = field
169
+
170
+ if (required && label && !nonRequiredFieldsLabel.includes(type)) {
171
+ field.label = `${label}*`
172
+ }
173
+
167
174
  return field
168
175
  },
169
176
 
@@ -46,7 +46,7 @@ props:
46
46
 
47
47
  gutter:
48
48
  desc: Espaçamento entre colunas.
49
- default: md
49
+ default: lg
50
50
  type: [String, Boolean]
51
51
  examples: [xs, sm, md, lg, xl, false]
52
52
 
@@ -28,7 +28,7 @@ props:
28
28
 
29
29
  gutter:
30
30
  desc: Espaçamento entre colunas.
31
- default: md
31
+ default: lg
32
32
  type: String
33
33
  examples: [xs, sm, md, lg, xl]
34
34
 
@@ -16,9 +16,8 @@
16
16
 
17
17
  <div v-else-if="!mx_isFetching">
18
18
  <slot name="empty-results">
19
- <div class="q-my-xl text-center">
20
- <q-icon class="q-mb-sm text-center" color="grey-7" name="o_search" size="38px" />
21
- <div class="text-grey-7">Nenhum item encontrado.</div>
19
+ <div class="q-my-lg text-body1 text-grey-7">
20
+ Não itens para serem exibidos.
22
21
  </div>
23
22
  </slot>
24
23
  </div>
@@ -27,8 +26,8 @@
27
26
  <q-spinner color="grey" size="3em" />
28
27
  </div>
29
28
 
30
- <div v-if="hasPages" class="justify-center q-mt-lg">
31
- <q-pagination v-model="page" boundary-links class="flex-center" direction-links :input="$q.screen.lt.sm" :max="totalPages" :max-pages="6" @click="changePage" />
29
+ <div v-if="hasPages" class="flex items-center q-mt-sm" :class="paginationClasses">
30
+ <qas-pagination v-model="page" :max="totalPages" @click="changePage" />
32
31
  </div>
33
32
 
34
33
  <q-inner-loading :showing="hasResults && mx_isFetching">
@@ -44,12 +43,14 @@
44
43
  <script>
45
44
  import { viewMixin, contextMixin } from '../../mixins'
46
45
  import QasFilters from '../filters/QasFilters.vue'
46
+ import QasPagination from '../pagination/QasPagination.vue'
47
47
  import { extend } from 'quasar'
48
48
  import { getState, getAction } from '@bildvitta/store-adapter'
49
49
 
50
50
  export default {
51
51
  components: {
52
- QasFilters
52
+ QasFilters,
53
+ QasPagination
53
54
  },
54
55
 
55
56
  mixins: [contextMixin, viewMixin],
@@ -65,6 +66,10 @@ export default {
65
66
  type: Array
66
67
  },
67
68
 
69
+ useAutoHandleOnDelete: {
70
+ type: Boolean
71
+ },
72
+
68
73
  useRefresh: {
69
74
  default: true,
70
75
  type: Boolean
@@ -89,7 +94,8 @@ export default {
89
94
 
90
95
  data () {
91
96
  return {
92
- page: 1
97
+ page: 1,
98
+ resultsQuantity: 0
93
99
  }
94
100
  },
95
101
 
@@ -116,6 +122,10 @@ export default {
116
122
 
117
123
  showResults () {
118
124
  return this.hasResults || this.useResultsAreaOnly
125
+ },
126
+
127
+ paginationClasses () {
128
+ return this.$qas.screen.isSmall ? 'justify-center column' : 'justify-end'
119
129
  }
120
130
  },
121
131
 
@@ -142,6 +152,18 @@ export default {
142
152
  this.setCurrentPage()
143
153
  },
144
154
 
155
+ mounted () {
156
+ if (!this.useAutoHandleOnDelete) return
157
+
158
+ window.addEventListener('delete-success', this.onDeleteResult)
159
+ },
160
+
161
+ unmounted () {
162
+ if (!this.useAutoHandleOnDelete) return
163
+
164
+ window.removeEventListener('delete-success', this.onDeleteResult)
165
+ },
166
+
145
167
  methods: {
146
168
  changePage () {
147
169
  const query = { ...this.$route.query, page: this.page }
@@ -168,7 +190,8 @@ export default {
168
190
  payload
169
191
  })
170
192
 
171
- const { errors, fields, metadata } = response.data
193
+ const { errors, fields, metadata, results } = response.data
194
+ this.resultsQuantity = results.length
172
195
 
173
196
  this.mx_setErrors(errors)
174
197
  this.mx_setFields(fields)
@@ -210,6 +233,42 @@ export default {
210
233
 
211
234
  setCurrentPage () {
212
235
  this.page = parseInt(this.$route.query.page || 1)
236
+ },
237
+
238
+ onDeleteResult ({ detail: { entity } }) {
239
+ const { page } = this.mx_context
240
+
241
+ /*
242
+ * - se a entidade que estiver sendo excluída for diferente da entidade da listagem, ignora.
243
+ * - se a ultima pagina da paginação for igual a pagina atual e tiver mais de um resultado, ignora.
244
+ * - se não existir paginação (somente 1), ignora.
245
+ */
246
+ const skipRefreshList = [
247
+ (entity !== this.entity),
248
+ (this.totalPages === page && this.resultsQuantity > 1),
249
+ (this.totalPages === 1)
250
+ ]
251
+
252
+ this.resultsQuantity -= 1
253
+
254
+ if (skipRefreshList.find(Boolean)) return
255
+
256
+ /*
257
+ * caso eu remova o ultimo item da ultima pagina eu volto ele para a pagina anterior
258
+ * ex: estou na pagina 3 que é a ultima pagina, e removo o ultimo item dela, eu volto o usuário para pagina 2
259
+ */
260
+ if (this.resultsQuantity === 1 || !this.resultsQuantity) {
261
+ const { path, query } = this.$route
262
+
263
+ this.$router.replace({ path, query: { ...query, page: query.page - 1 } })
264
+ return
265
+ }
266
+
267
+ /*
268
+ * caso remova algo de uma pagina que não seja a ultima, chama o método fetchList novamente
269
+ * ex: estou na pagina 2 e existem 3 paginas, removo um item da pagina 2, então chamo o método fetchList
270
+ */
271
+ this.mx_fetchHandler({ ...this.mx_context, url: this.url }, this.fetchList)
213
272
  }
214
273
  }
215
274
  }
@@ -58,6 +58,10 @@ props:
58
58
  desc: Envia como parâmetro para a action "fetchList" do modulo correspondente a "entity".
59
59
  type: String
60
60
 
61
+ use-auto-handle-on-delete:
62
+ desc: Controla se o componente vai lidar automaticamente quando acontecer algum delete compatível com a listagem.
63
+ type: Boolean
64
+
61
65
  use-boundary:
62
66
  desc: Controla o limite que o FormView terá, quando é "false", a tag pai deixa de ser um "QPage" para ser uma "div" e é removido as classes "container" e "spaced", comumente utilizando quando precisa usar o QasFormView dentro de um dialog.
63
67
  default: true
@@ -11,11 +11,7 @@
11
11
  <div>
12
12
  <div class="flex items-center justify-between q-py-xs">
13
13
  <qas-label v-if="!useSingleLabel" :label="getRowLabel(index)" />
14
-
15
- <div v-if="hasBlockActions(row)" class="q-gutter-x-sm">
16
- <qas-btn v-if="useDuplicate" v-bind="buttonDuplicateProps" @click="add(row)" />
17
- <qas-btn v-if="showDestroyBtn" v-bind="buttonDestroyProps" @click="destroy(index, row)" />
18
- </div>
14
+ <qas-actions-menu v-if="hasBlockActions(row)" :list="getActionsList(index, row)" :use-label-on-small-screen="false" />
19
15
  </div>
20
16
 
21
17
  <div ref="formGenerator" class="col-12 justify-between q-col-gutter-x-md row">
@@ -28,12 +24,7 @@
28
24
  </slot>
29
25
 
30
26
  <div v-if="hasInlineActions(row)" class="flex items-center qas-nested-fields__actions">
31
- <div class="col-auto">
32
- <qas-btn v-if="useDuplicate" color="primary" flat icon="o_content_copy" round @click="add(row)" />
33
- </div>
34
- <div class="col-auto">
35
- <qas-btn v-if="showDestroyBtn" color="negative" flat icon="o_cancel" round @click="destroy(index, row)" />
36
- </div>
27
+ <qas-actions-menu :list="getActionsList(index, row)" :use-label-on-small-screen="false" />
37
28
  </div>
38
29
  </div>
39
30
 
@@ -47,18 +38,22 @@
47
38
 
48
39
  <div v-if="useAdd" class="q-mt-md">
49
40
  <slot :add="add" name="add-input">
50
- <div v-if="useInlineActions" class="cursor-pointer items-center q-col-gutter-x-md q-mt-md row" @click="add()">
41
+ <div v-if="showAddFirstInputButton" class="text-left">
42
+ <qas-btn class="q-px-sm" color="dark" flat @click="add()">{{ addFirstInputLabel }}</qas-btn>
43
+ </div>
44
+
45
+ <div v-else-if="useInlineActions" class="cursor-pointer items-center q-col-gutter-x-md q-mt-md row" @click="add()">
51
46
  <div class="col">
52
47
  <qas-input class="disabled no-pointer-events" hide-bottom-space :label="addInputLabel" outlined @focus="add()" />
53
48
  </div>
54
49
 
55
50
  <div class="col-auto">
56
- <qas-btn color="green" flat icon="o_add_circle_outline" round />
51
+ <qas-btn color="dark" flat icon="o_add_circle_outline" round />
57
52
  </div>
58
53
  </div>
59
54
 
60
- <div v-else class="q-mt-lg">
61
- <qas-btn class="full-width q-py-md" icon="o_add" outline @click="add()">{{ addInputLabel }}</qas-btn>
55
+ <div v-else class="text-left">
56
+ <qas-btn class="q-px-sm" color="dark" flat icon="o_add" @click="add()">{{ addInputLabel }}</qas-btn>
62
57
  </div>
63
58
  </slot>
64
59
  </div>
@@ -67,6 +62,7 @@
67
62
  </template>
68
63
 
69
64
  <script>
65
+ import QasActionsMenu from '../actions-menu/QasActionsMenu.vue'
70
66
  import QasBtn from '../btn/QasBtn.vue'
71
67
  import QasFormGenerator from '../form-generator/QasFormGenerator.vue'
72
68
  import QasInput from '../input/QasInput.vue'
@@ -80,27 +76,38 @@ export default {
80
76
  name: 'QasNestedFields',
81
77
 
82
78
  components: {
79
+ QasActionsMenu,
83
80
  QasBtn,
84
81
  QasFormGenerator,
85
82
  QasInput,
86
83
  QasLabel,
87
84
 
88
- // vue
85
+ // Vue
89
86
  TransitionGroup
90
87
  },
91
88
 
92
89
  props: {
90
+ actionsMenuProps: {
91
+ type: Object,
92
+ default: () => ({})
93
+ },
94
+
95
+ addFirstInputLabel: {
96
+ type: String,
97
+ default: 'Clique aqui para adicionar'
98
+ },
99
+
93
100
  addInputLabel: {
94
101
  type: String,
95
- default: 'Inserir novo campo'
102
+ default: 'Adicionar'
96
103
  },
97
104
 
98
105
  buttonDestroyProps: {
99
106
  type: Object,
100
107
  default: () => {
101
108
  return {
102
- label: 'Remover',
103
- icon: 'o_cancel',
109
+ label: 'Excluir',
110
+ icon: 'o_delete',
104
111
  flat: true,
105
112
  dense: true
106
113
  }
@@ -190,7 +197,8 @@ export default {
190
197
  },
191
198
 
192
199
  useDestroyAlways: {
193
- type: Boolean
200
+ type: Boolean,
201
+ default: undefined
194
202
  },
195
203
 
196
204
  useDuplicate: {
@@ -198,6 +206,11 @@ export default {
198
206
  default: true
199
207
  },
200
208
 
209
+ useFirstInputButton: {
210
+ type: Boolean,
211
+ default: true
212
+ },
213
+
201
214
  useIndexLabel: {
202
215
  type: Boolean
203
216
  },
@@ -215,6 +228,11 @@ export default {
215
228
  default: true
216
229
  },
217
230
 
231
+ useStartsEmpty: {
232
+ default: true,
233
+ type: Boolean
234
+ },
235
+
218
236
  modelValue: {
219
237
  type: Array,
220
238
  default: () => []
@@ -225,25 +243,35 @@ export default {
225
243
 
226
244
  data () {
227
245
  return {
228
- nested: []
246
+ nested: [],
247
+ hasDestroyAlways: true
229
248
  }
230
249
  },
231
250
 
232
251
  computed: {
233
- fieldLabel () {
234
- return this.field?.label
252
+ children () {
253
+ return this.field?.children
235
254
  },
236
255
 
237
- fieldName () {
238
- return this.field?.name
256
+ componentTag () {
257
+ return this.useAnimation ? 'transition-group' : 'div'
239
258
  },
240
259
 
241
- children () {
242
- return this.field?.children
260
+ componentProps () {
261
+ if (!this.useAnimation) return {}
262
+
263
+ return {
264
+ tag: 'div',
265
+ enterActiveClass: 'animated slideInDown'
266
+ }
243
267
  },
244
268
 
245
- showDestroyBtn () {
246
- return this.nested.filter(item => !item[this.destroyKey]).length > 1 || this.useDestroyAlways
269
+ fieldLabel () {
270
+ return this.field?.label
271
+ },
272
+
273
+ fieldName () {
274
+ return this.field?.name
247
275
  },
248
276
 
249
277
  formClasses () {
@@ -253,21 +281,16 @@ export default {
253
281
  }
254
282
  },
255
283
 
256
- transformedErrors () {
257
- return Array.isArray(this.errors) ? this.errors : constructObject(this.fieldName, this.errors)
284
+ showDestroyBtn () {
285
+ return this.nested.filter(item => !item[this.destroyKey]).length > 1 || this.useDestroyAlways
258
286
  },
259
287
 
260
- componentTag () {
261
- return this.useAnimation ? 'transition-group' : 'div'
288
+ transformedErrors () {
289
+ return Array.isArray(this.errors) ? this.errors : constructObject(this.fieldName, this.errors)
262
290
  },
263
291
 
264
- componentProps () {
265
- if (!this.useAnimation) return {}
266
-
267
- return {
268
- tag: 'div',
269
- enterActiveClass: 'animated slideInDown'
270
- }
292
+ showAddFirstInputButton () {
293
+ return this.useFirstInputButton && !this.nested.length
271
294
  }
272
295
  },
273
296
 
@@ -282,13 +305,49 @@ export default {
282
305
 
283
306
  rowObject: {
284
307
  handler () {
285
- if (!this.nested.length) return this.setDefaultNestedValue()
308
+ this.setDefaultNestedValue()
309
+ },
310
+ immediate: true
311
+ },
312
+
313
+ useDestroyAlways: {
314
+ handler (value) {
315
+ this.hasDestroyAlways = value ?? this.useStartsEmpty
286
316
  },
287
317
  immediate: true
288
318
  }
289
319
  },
290
320
 
291
321
  methods: {
322
+ getActionsList (index, row) {
323
+ const list = {}
324
+
325
+ if (this.useDuplicate) {
326
+ list.duplicate = {
327
+ ...this.buttonDuplicateProps,
328
+ handler: () => this.add(row)
329
+ }
330
+ }
331
+
332
+ if (this.showDestroyBtn) {
333
+ list.destroy = {
334
+ ...this.buttonDestroyProps,
335
+ handler: () => this.destroy(index, row)
336
+ }
337
+ }
338
+
339
+ for (const key in this.actionsMenuProps.list) {
340
+ const { handler, ...content } = this.actionsMenuProps.list[key] || {}
341
+
342
+ list[key] = {
343
+ handler: payload => handler?.({ payload, row, index }),
344
+ ...content
345
+ }
346
+ }
347
+
348
+ return list
349
+ },
350
+
292
351
  add (row = {}) {
293
352
  const payload = { ...this.rowObject, ...row }
294
353
  const hasIdentifierKey = payload[this.identifierItemKey]
@@ -345,6 +404,7 @@ export default {
345
404
  },
346
405
 
347
406
  setDefaultNestedValue () {
407
+ if (this.nested.length || this.useStartsEmpty) return
348
408
  this.nested.splice(0, 0, { ...this.rowObject })
349
409
  },
350
410
 
@@ -4,14 +4,24 @@ meta:
4
4
  desc: Componente para gerar dinamicamente campos nested.
5
5
 
6
6
  props:
7
+ actions-menu-props:
8
+ desc: Repassa as propriedades para o componente QasActionsMenu.
9
+ default: {}
10
+ type: Object
11
+
12
+ add-first-input-label:
13
+ desc: Rótulo do input para adicionar o primeiro campo.
14
+ default: Clique aqui para adicionar
15
+ type: String
16
+
7
17
  add-input-label:
8
18
  desc: Rótulo do input de adicionar novos campos.
9
- default: Inserir novo campo
10
- type: Boolean
19
+ default: Adicionar
20
+ type: String
11
21
 
12
22
  button-destroy-props:
13
23
  desc: Props do botão de excluir linha contendo os campos.
14
- default: "{ label: 'Remover', o_cancel, flat: true, dense: true }"
24
+ default: "{ label: 'Excluir', icon: 'o_delete', flat: true, dense: true }"
15
25
  debugger: true
16
26
  type: Object
17
27
 
@@ -76,17 +86,17 @@ props:
76
86
  model: true
77
87
 
78
88
  row-label:
79
- desc: Rótulo entre cada linha (row).
89
+ desc: Rótulo entre cada linha.
80
90
  type: String
81
91
 
82
92
  row-object:
83
- desc: Objeto contendo valores iniciais do model de cada linha (row).
93
+ desc: Objeto contendo valores iniciais do model de cada linha.
84
94
  default: {}
85
95
  type: Object
86
96
  examples: ["{ name: '', cities: [] }"]
87
97
 
88
98
  use-add:
89
- desc: Controla quando vai ter seção de "adicionar" novas rows (linhas).
99
+ desc: Controla quando vai ter seção de "adicionar" novas linhas.
90
100
  default: true
91
101
  type: Boolean
92
102
 
@@ -96,7 +106,7 @@ props:
96
106
  type: Boolean
97
107
 
98
108
  use-destroy-always:
99
- desc: Controla o botão de remover em todas linhas (row), incluindo a primeira.
109
+ desc: Controla o botão de remover em todas linhas, incluindo a primeira.
100
110
  type: Boolean
101
111
 
102
112
  use-duplicate:
@@ -104,20 +114,30 @@ props:
104
114
  default: true
105
115
  type: Boolean
106
116
 
117
+ use-first-input-button:
118
+ desc: Controla se vai ter um botão diferente para adicionar o primeiro campo.
119
+ default: true
120
+ type: Boolean
121
+
107
122
  use-index-label:
108
123
  desc: Se tiver "rowLabel" esta prop controla se cada label da linha vai ter o index como sufixo.
109
124
  type: Boolean
110
125
 
111
126
  use-inline-actions:
112
- desc: Controla o comportamento referente aos estilos das ações de duplicar/adicionar/remover
127
+ desc: Controla o comportamento referente aos estilos das ações de duplicar/adicionar/remover.
113
128
  type: Boolean
114
129
 
115
130
  use-single-label:
116
- desc: Se o valor for "true", então vai exibir apenas uma label referente a todas linhas (row) e não uma por linha
131
+ desc: Exibe apenas uma label referente a todas linhas, e não uma por linha.
117
132
  type: Boolean
118
133
 
119
134
  use-remove-on-destroy:
120
- desc: Se o valor for "true" o valor do model será removido senão será adicionar uma flag como `destroyed` por exemplo.
135
+ desc: O valor do model será removido senão será adicionar uma flag como `destroyed` por exemplo.
136
+ default: true
137
+ type: Boolean
138
+
139
+ use-starts-empty:
140
+ desc: O componente iniciará sem nenhuma linha.
121
141
  default: true
122
142
  type: Boolean
123
143
 
@@ -0,0 +1,27 @@
1
+ <template>
2
+ <q-pagination v-bind="defaultProps" />
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ name: 'QasPagination',
8
+
9
+ computed: {
10
+ defaultProps () {
11
+ const { modelValue, ...attributes } = this.$attrs
12
+
13
+ return {
14
+ activeColor: 'primary',
15
+ activeDesign: 'flat',
16
+ boundaryNumbers: true,
17
+ color: 'grey-7',
18
+ directionLinks: true,
19
+ maxPages: modelValue < 3 ? 3 : 6,
20
+ modelValue,
21
+
22
+ ...attributes
23
+ }
24
+ }
25
+ }
26
+ }
27
+ </script>
@@ -0,0 +1,4 @@
1
+ type: component
2
+
3
+ meta:
4
+ desc: Componente para paginação de listagens.
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <qas-box class="q-mb-xl q-px-lg q-py-md">
2
+ <qas-box class="q-px-lg q-py-md">
3
3
  <q-table ref="table" class="bg-transparent qas-table-generator" :class="tableClass" v-bind="attributes">
4
4
  <template v-for="(_, name) in $slots" #[name]="context">
5
5
  <slot v-if="hasBodySlot" name="body" :props="context" />
@@ -70,7 +70,10 @@ export default {
70
70
  flat: true,
71
71
  hideBottom: true,
72
72
  pagination: { rowsPerPage: 0 },
73
- rowKey: this.rowKey
73
+ rowKey: this.rowKey,
74
+
75
+ // Eventos.
76
+ onRowClick: this.$attrs.onRowClick && this.onRowClick
74
77
  }
75
78
 
76
79
  return attributes
@@ -220,6 +223,11 @@ export default {
220
223
  }
221
224
  },
222
225
 
226
+ onRowClick () {
227
+ if (this.hasScrollOnGrab && this.scrollOnGrab.haveMoved()) return
228
+ this.$attrs.onRowClick(...arguments)
229
+ },
230
+
223
231
  setObserver () {
224
232
  this.elementToObserve = this.getTableElement()
225
233
  this.resizeObserver = new ResizeObserver(entries => {
@@ -44,3 +44,17 @@ slots:
44
44
  desc: Payload da linha da tabela.
45
45
  default: {}
46
46
  type: Object
47
+
48
+ events:
49
+ '@row-click -> function(event, row, index)':
50
+ desc: Dispara ao clicar em uma linha. Não funciona usando slots personalizados (body, row e item).
51
+ params:
52
+ event:
53
+ desc: Evento do click.
54
+ type: Event
55
+ row:
56
+ desc: Payload da linha da tabela.
57
+ type: Object
58
+ index:
59
+ desc: Índice da linha.
60
+ type: Number
@@ -4,7 +4,7 @@
4
4
  <div ref="truncate" :class="truncateTextClass">
5
5
  <slot>{{ text }}</slot>
6
6
  </div>
7
- <div v-if="isTruncated" class="cursor-pointer text-primary" @click="toggleDialog">{{ seeMoreLabel }}</div>
7
+ <div v-if="isTruncated" class="cursor-pointer text-primary" @click.stop="toggleDialog">{{ seeMoreLabel }}</div>
8
8
  </div>
9
9
 
10
10
  <qas-dialog v-model="showDialog" v-bind="defaultDialogProps">
@@ -1,6 +1,10 @@
1
1
  .q-item {
2
2
  font-size: 16px;
3
3
 
4
+ &:not(.disabled) {
5
+ font-weight: 600;
6
+ }
7
+
4
8
  &.q-router-link--active {
5
9
  background-color: transparent !important;
6
10
  font-weight: 600;
@@ -0,0 +1 @@
1
+ @import './notify';
@@ -0,0 +1,40 @@
1
+ .q-notification {
2
+ margin-top: 80px;
3
+
4
+ &__content {
5
+ .q-icon {
6
+ margin-right: var(--qas-spacing-sm);
7
+ }
8
+ }
9
+
10
+ &__actions {
11
+ align-self: start;
12
+ padding-top: var(--qas-spacing-xs);
13
+
14
+ .q-btn {
15
+ color: white;
16
+ height: var(--qas-spacing-md);
17
+ min-height: auto;
18
+ min-width: auto;
19
+ padding: 0;
20
+ text-align: center;
21
+ text-transform: none;
22
+ width: var(--qas-spacing-md);
23
+
24
+ &__content {
25
+ height: var(--qas-spacing-md);
26
+
27
+ span {
28
+ align-self: center;
29
+ height: var(--qas-spacing-md);
30
+ line-height: 0.9;
31
+ width: var(--qas-spacing-md);
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ @media (max-width: $breakpoint-xs) {
38
+ margin-top: 40px;
39
+ }
40
+ }
@@ -27,15 +27,15 @@ $space-md: (
27
27
  y: $space-y-base
28
28
  );
29
29
 
30
- // O tamanho original seria "16px * 1.5" ou "24px", alterado para ter "32px"
31
30
  $space-lg: (
32
- x: ($space-x-base * 2),
33
- y: ($space-y-base * 2)
31
+ x: ($space-x-base * 1.5),
32
+ y: ($space-y-base * 1.5)
34
33
  );
35
34
 
35
+ // O tamanho original seria "16px * 3" ou "48px", alterado para ter "32px"
36
36
  $space-xl: (
37
- x: ($space-x-base * 3),
38
- y: ($space-y-base * 3)
37
+ x: ($space-x-base * 2),
38
+ y: ($space-y-base * 2)
39
39
  );
40
40
 
41
41
  $spaces: (
@@ -2,6 +2,7 @@ export default function (element) {
2
2
  setStyle()
3
3
 
4
4
  let isDown = false
5
+ let moved = false
5
6
  let startX
6
7
  let scrollLeft
7
8
 
@@ -12,35 +13,38 @@ export default function (element) {
12
13
 
13
14
  function onMouseDown (event) {
14
15
  isDown = true
16
+ moved = false
17
+
15
18
  element.classList.add('active')
19
+
16
20
  startX = event.pageX - element.offsetLeft
17
21
  scrollLeft = element.scrollLeft
18
22
  }
19
23
 
20
24
  function onMouseLeave () {
21
25
  isDown = false
22
- element.classList.remove('active')
23
26
 
27
+ element.classList.remove('active')
24
28
  setStyle()
25
29
  }
26
30
 
27
31
  function onMouseUp () {
28
32
  isDown = false
29
- element.classList.remove('active')
30
33
 
34
+ element.classList.remove('active')
31
35
  setStyle()
32
36
  }
33
37
 
34
38
  function onMouseMove (event) {
39
+ if (event) event.preventDefault()
35
40
  if (!isDown) return
36
41
 
37
- event.preventDefault()
38
-
39
42
  setStyle('grabbing')
40
43
 
41
44
  const x = event.pageX - element.offsetLeft
42
45
  const walk = (x - startX) * 3 // scroll-fast
43
46
  element.scrollLeft = scrollLeft - walk
47
+ moved = true
44
48
  }
45
49
 
46
50
  function setStyle (model = 'grab') {
@@ -54,8 +58,13 @@ export default function (element) {
54
58
  element.removeEventListener('mousemove', onMouseMove)
55
59
  }
56
60
 
61
+ function haveMoved () {
62
+ return moved
63
+ }
64
+
57
65
  return {
58
66
  element,
67
+ haveMoved,
59
68
  destroyEvents
60
69
  }
61
70
  }
package/src/index.scss CHANGED
@@ -22,8 +22,11 @@ $generic-border-radius: var(--qas-generic-border-radius);
22
22
  // components
23
23
  @import './css/components/index';
24
24
 
25
+ // plugins
26
+ @import './css/plugins/index';
27
+
25
28
  // utils
26
29
  @import './css/utils/index';
27
30
 
28
31
  // mixins
29
- @import './css/mixins/index'
32
+ @import './css/mixins/index';
@@ -13,7 +13,7 @@ export default {
13
13
  },
14
14
 
15
15
  gutter: {
16
- default: 'md',
16
+ default: 'lg',
17
17
  type: [String, Boolean],
18
18
  validator: value => {
19
19
  return typeof value === 'boolean' || ['xs', 'sm', 'md', 'lg', 'xl'].includes(value)
@@ -1,10 +1,12 @@
1
1
  import { Notify } from 'quasar'
2
+ import notifyConfig from '../../shared/notify-config.js'
2
3
 
3
- Notify.registerType('error', {
4
- color: 'negative',
5
- progress: true
6
- })
4
+ Notify.registerType('error', { icon: 'o_cancel', ...notifyConfig })
7
5
 
8
6
  export default (message, caption) => {
9
- Notify.create({ caption, message, type: 'error' })
7
+ Notify.create({
8
+ caption,
9
+ message,
10
+ type: 'error'
11
+ })
10
12
  }
@@ -1,10 +1,12 @@
1
1
  import { Notify } from 'quasar'
2
+ import notifyConfig from '../../shared/notify-config.js'
2
3
 
3
- Notify.registerType('success', {
4
- icon: 'o_check',
5
- progress: true
6
- })
4
+ Notify.registerType('success', { icon: 'o_check_circle', ...notifyConfig })
7
5
 
8
6
  export default (message, caption) => {
9
- Notify?.create({ caption, message, type: 'success' })
7
+ Notify.create({
8
+ caption,
9
+ message,
10
+ type: 'success'
11
+ })
10
12
  }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ color: 'grey-9',
3
+ progress: true,
4
+ closeBtn: 'x',
5
+ position: 'top',
6
+ timeout: 4000
7
+ }
package/src/vue-plugin.js CHANGED
@@ -31,6 +31,7 @@ import QasNestedFields from './components/nested-fields/QasNestedFields.vue'
31
31
  import QasNumericInput from './components/numeric-input/QasNumericInput.vue'
32
32
  import QasOptionGroup from './components/option-group/QasOptionGroup.vue'
33
33
  import QasPageHeader from './components/page-header/QasPageHeader.vue'
34
+ import QasPagination from './components/pagination/QasPagination.vue'
34
35
  import QasPasswordInput from './components/password-input/QasPasswordInput.vue'
35
36
  import QasPasswordStrengthChecker from './components/password-strength-checker/QasPasswordStrengthChecker.vue'
36
37
  import QasProfile from './components/profile/QasProfile.vue'
@@ -102,6 +103,7 @@ function install (app) {
102
103
  app.component('QasNumericInput', QasNumericInput)
103
104
  app.component('QasOptionGroup', QasOptionGroup)
104
105
  app.component('QasPageHeader', QasPageHeader)
106
+ app.component('QasPagination', QasPagination)
105
107
  app.component('QasPasswordInput', QasPasswordInput)
106
108
  app.component('QasPasswordStrengthChecker', QasPasswordStrengthChecker)
107
109
  app.component('QasProfile', QasProfile)
@@ -174,6 +176,7 @@ export {
174
176
  QasNumericInput,
175
177
  QasOptionGroup,
176
178
  QasPageHeader,
179
+ QasPagination,
177
180
  QasPasswordInput,
178
181
  QasPasswordStrengthChecker,
179
182
  QasProfile,