@weni/unnnic-system 2.35.0 → 2.36.1-alpha.0

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@weni/unnnic-system",
3
- "version": "2.35.0",
3
+ "version": "2.36.1-alpha.0",
4
4
  "type": "commonjs",
5
5
  "files": [
6
6
  "dist",
@@ -87,4 +87,4 @@
87
87
  "vitest": "^1.6.0",
88
88
  "vue-eslint-parser": "^9.4.2"
89
89
  }
90
- }
90
+ }
@@ -0,0 +1,493 @@
1
+ <template>
2
+ <table
3
+ class="unnnic-data-table"
4
+ :style="props.fixedHeaders ? {} : heightStyles"
5
+ >
6
+ <thead
7
+ v-if="!shouldHideHeaders"
8
+ class="unnnic-data-table__header"
9
+ >
10
+ <tr class="unnnic-data-table__header-row">
11
+ <th
12
+ v-for="header in headers"
13
+ :key="header.itemKey"
14
+ :class="[
15
+ 'unnnic-data-table__header-cell',
16
+ {
17
+ 'unnnic-data-table__header-cell--clickable': header.isSortable,
18
+ 'unnnic-data-table__header-cell--sorting':
19
+ sort.header === header.title && sort.order !== '',
20
+ },
21
+ ]"
22
+ @click.stop="handleClickHeader(header)"
23
+ >
24
+ <slot
25
+ v-if="slots[`header-${header.itemKey}`]"
26
+ :name="`header-${header.itemKey}`"
27
+ :header="header"
28
+ />
29
+ <template v-else>
30
+ {{ header.title }}
31
+ </template>
32
+ <template v-if="header.isSortable">
33
+ <IconArrowsDefault
34
+ v-if="sort.header !== header.title"
35
+ class="order-default-icon"
36
+ data-testid="arrow-default-icon"
37
+ />
38
+ <Icon
39
+ v-else-if="sort.order === 'asc'"
40
+ clickable
41
+ size="ant"
42
+ :icon="'switch_left'"
43
+ style="transform: rotate(-90deg)"
44
+ data-testid="arrow-asc-icon"
45
+ />
46
+ <Icon
47
+ v-else-if="sort.order === 'desc'"
48
+ clickable
49
+ size="ant"
50
+ :icon="'switch_left'"
51
+ style="transform: rotate(90deg)"
52
+ data-testid="arrow-desc-icon"
53
+ />
54
+ </template>
55
+ </th>
56
+ </tr>
57
+ </thead>
58
+ <tbody
59
+ :class="[
60
+ 'unnnic-data-table__body',
61
+ { 'unnnic-data-table__body--hide-headers': props.hideHeaders },
62
+ ]"
63
+ :style="props.fixedHeaders ? heightStyles : {}"
64
+ >
65
+ <tr
66
+ v-if="isLoading"
67
+ :class="[
68
+ 'unnnic-data-table__body-row',
69
+ 'unnnic-data-table__body-row--loading',
70
+ ]"
71
+ >
72
+ <img
73
+ class="unnnic-data-table__body-cell--loading"
74
+ data-testid="body-row-loading"
75
+ src="../../assets/icons/weni-loading.svg"
76
+ height="40"
77
+ />
78
+ </tr>
79
+ <template v-else-if="props.items.length">
80
+ <tr
81
+ v-for="(item, index) in props.items"
82
+ :key="index"
83
+ :class="[
84
+ 'unnnic-data-table__body-row',
85
+ { 'unnnic-data-table__body-row--clickable': props.clickable },
86
+ ]"
87
+ @click="handleClickRow(item)"
88
+ >
89
+ <template
90
+ v-for="key in headersItemsKeys"
91
+ :key="key"
92
+ >
93
+ <td
94
+ v-if="slots[`body-${key}`]"
95
+ :class="[
96
+ 'unnnic-data-table__body-cell',
97
+ `unnnic-data-table__body-cell--${size}`,
98
+ ]"
99
+ >
100
+ <slot
101
+ :name="`body-${key}`"
102
+ :item="item"
103
+ />
104
+ </td>
105
+ <td
106
+ v-else
107
+ :class="[
108
+ 'unnnic-data-table__body-cell',
109
+ `unnnic-data-table__body-cell--${size}`,
110
+ ]"
111
+ >
112
+ {{ item[key] }}
113
+ </td>
114
+ </template>
115
+ </tr>
116
+ </template>
117
+ <tr
118
+ v-else
119
+ class="unnnic-data-table__body-row"
120
+ >
121
+ <td
122
+ v-if="slots['without-results']"
123
+ :class="[
124
+ 'unnnic-data-table__body-cell',
125
+ `unnnic-data-table__body-cell--${size}`,
126
+ ]"
127
+ >
128
+ <slot name="without-results" />
129
+ </td>
130
+ <td
131
+ v-else
132
+ :class="[
133
+ 'unnnic-data-table__body-cell',
134
+ `unnnic-data-table__body-cell--${size}`,
135
+ ]"
136
+ data-testid="body-cell"
137
+ >
138
+ <p
139
+ class="unnnic-data-table__body-cell-text"
140
+ data-testid="body-cell-text"
141
+ >
142
+ {{ defaultTranslations.without_results[props.locale || 'en'] }}
143
+ </p>
144
+ </td>
145
+ </tr>
146
+ </tbody>
147
+ <TablePagination
148
+ v-if="!props.hidePagination"
149
+ :modelValue="props.page"
150
+ :total="props.pageTotal"
151
+ :interval="props.pageInterval"
152
+ @update:model-value="$emit('update:page', $event)"
153
+ />
154
+ </table>
155
+ </template>
156
+
157
+ <script lang="ts">
158
+ export default {
159
+ name: 'UnnnicDataTable',
160
+ };
161
+ </script>
162
+
163
+ <script setup lang="ts">
164
+ import { computed, ref, useSlots } from 'vue';
165
+
166
+ import Icon from '../Icon.vue';
167
+ import IconArrowsDefault from '../icons/iconArrowsDefault.vue';
168
+ import TablePagination from '../TableNext/TablePagination.vue';
169
+
170
+ type DataTableHeader = {
171
+ title: string;
172
+ isSortable?: boolean;
173
+ itemKey: string;
174
+ align?: 'start' | 'center' | 'end';
175
+ size?: number | string;
176
+ };
177
+
178
+ type DataTableItem = {
179
+ [key: string]: any;
180
+ };
181
+
182
+ const slots = useSlots();
183
+
184
+ const emit = defineEmits<{
185
+ 'update:sort': [sort: { header: string; order: string }];
186
+ itemClick: [item: DataTableItem];
187
+ 'update:page': [page: number];
188
+ }>();
189
+
190
+ const props = defineProps<{
191
+ headers: {
192
+ type: DataTableHeader[];
193
+ required: true;
194
+ };
195
+ items: {
196
+ type: DataTableItem[];
197
+ required: true;
198
+ };
199
+ isLoading: {
200
+ type: Boolean;
201
+ default: false;
202
+ };
203
+ size: {
204
+ type: 'sm' | 'md';
205
+ default: 'md';
206
+ };
207
+ height: {
208
+ type: String;
209
+ default: '';
210
+ };
211
+ maxHeight: {
212
+ type: String;
213
+ default: '';
214
+ };
215
+ clickable: {
216
+ type: Boolean;
217
+ default: false;
218
+ };
219
+ fixedHeaders: {
220
+ type: Boolean;
221
+ default: false;
222
+ };
223
+ hideHeaders: {
224
+ type: Boolean;
225
+ default: false;
226
+ };
227
+ hidePagination: {
228
+ type: Boolean;
229
+ default: false;
230
+ };
231
+ page: {
232
+ type: Number;
233
+ default: 1;
234
+ };
235
+ pageTotal: {
236
+ type: Number;
237
+ default: 0;
238
+ };
239
+ pageInterval: {
240
+ type: Number;
241
+ default: 5;
242
+ };
243
+ locale: {
244
+ type: string;
245
+ default: 'en';
246
+ };
247
+ }>();
248
+
249
+ const defaultTranslations = {
250
+ without_results: {
251
+ 'pt-br': 'Nenhum resultado correspondente',
252
+ en: 'No matching results',
253
+ es: 'No hay resultados coincidentes',
254
+ },
255
+ };
256
+
257
+ const heightStyles = computed(() => {
258
+ return {
259
+ height: props.height || 'unset',
260
+ 'max-height': props.maxHeight || 'unset',
261
+ overflow: props.height || props.maxHeight ? 'auto' : 'unset',
262
+ };
263
+ });
264
+
265
+ const shouldHideHeaders = computed(() => {
266
+ return props.hideHeaders || !props.headers.length;
267
+ });
268
+
269
+ const headersItemsKeys: string[] = computed(() => {
270
+ return props.headers.map((header) => header.itemKey);
271
+ });
272
+
273
+ const sort = ref({
274
+ header: '',
275
+ order: '',
276
+ });
277
+
278
+ const getHeaderColumnSize = (header: DataTableHeader): string => {
279
+ return typeof header.size === 'number'
280
+ ? `${header.size || 1}fr`
281
+ : header.size || '1fr';
282
+ };
283
+
284
+ const gridTemplateColumns: string = computed(() => {
285
+ const columnSizes = props.headers.length
286
+ ? props.headers.map(getHeaderColumnSize)
287
+ : props.items[0].content.map(() => '1fr');
288
+
289
+ return columnSizes.join(' ');
290
+ });
291
+
292
+ const handleSort = (key: string, order: string) => {
293
+ sort.value = { header: key, order };
294
+ emit('update:sort', sort.value);
295
+ };
296
+
297
+ const handleClickHeader = (header: DataTableHeader) => {
298
+ if (!header.isSortable) return;
299
+
300
+ const nextSortOrderMapper = {
301
+ asc: 'desc',
302
+ desc: 'asc',
303
+ '': 'asc',
304
+ };
305
+
306
+ const nextSort =
307
+ header.title !== sort.value.header
308
+ ? 'asc'
309
+ : nextSortOrderMapper[sort.value.order];
310
+
311
+ handleSort(nextSort === '' ? '' : header.title, nextSort);
312
+ };
313
+
314
+ const handleClickRow = (item: DataTableItem) => {
315
+ if (!props.clickable) return;
316
+
317
+ emit('itemClick', item);
318
+ };
319
+ </script>
320
+
321
+ <style scoped lang="scss">
322
+ @use '@/assets/scss/unnnic' as *;
323
+
324
+ $tableBorder: $unnnic-border-width-thinner solid $unnnic-color-neutral-soft;
325
+
326
+ .unnnic-data-table {
327
+ border-spacing: 0;
328
+
329
+ overflow: hidden;
330
+
331
+ width: 100%;
332
+
333
+ display: flex;
334
+ flex-direction: column;
335
+
336
+ &::-webkit-scrollbar {
337
+ width: $unnnic-spacing-inline-nano;
338
+ }
339
+
340
+ &::-webkit-scrollbar-thumb {
341
+ background: $unnnic-color-neutral-cleanest;
342
+ border-radius: $unnnic-border-radius-pill;
343
+ }
344
+
345
+ &::-webkit-scrollbar-track {
346
+ background: $unnnic-color-neutral-soft;
347
+ border-radius: $unnnic-border-radius-pill;
348
+ }
349
+
350
+ &__header {
351
+ &-row {
352
+ @extend %base-row;
353
+
354
+ grid-template-columns: v-bind(gridTemplateColumns);
355
+ }
356
+
357
+ &-cell {
358
+ @extend %base-cell;
359
+
360
+ height: 100%;
361
+
362
+ box-sizing: border-box;
363
+ border: $tableBorder;
364
+ background-color: $unnnic-color-neutral-light;
365
+
366
+ font-weight: $unnnic-font-weight-bold;
367
+
368
+ display: flex;
369
+
370
+ &:first-of-type {
371
+ border-radius: $unnnic-border-radius-sm 0 0 0;
372
+ }
373
+
374
+ &:not(:first-of-type) {
375
+ border-left: none;
376
+ }
377
+
378
+ &:last-of-type {
379
+ border-radius: 0 $unnnic-border-radius-sm 0 0;
380
+ }
381
+
382
+ &--sorting {
383
+ background-color: $unnnic-color-neutral-soft;
384
+ }
385
+
386
+ &--clickable {
387
+ &:hover {
388
+ cursor: pointer;
389
+ background-color: $unnnic-color-neutral-soft;
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ &__body {
396
+ &::-webkit-scrollbar {
397
+ width: $unnnic-spacing-inline-nano;
398
+ }
399
+
400
+ &::-webkit-scrollbar-thumb {
401
+ background: $unnnic-color-neutral-cleanest;
402
+ border-radius: $unnnic-border-radius-pill;
403
+ }
404
+
405
+ &::-webkit-scrollbar-track {
406
+ background: $unnnic-color-neutral-soft;
407
+ border-radius: $unnnic-border-radius-pill;
408
+ }
409
+
410
+ &--hide-headers {
411
+ .unnnic-data-table__body-row:first-of-type {
412
+ border-radius: $unnnic-border-radius-sm $unnnic-border-radius-sm 0 0;
413
+ border-top: $tableBorder;
414
+ }
415
+ }
416
+
417
+ &-row {
418
+ @extend %base-row;
419
+
420
+ overflow: hidden;
421
+
422
+ border: $tableBorder;
423
+ border-collapse: collapse;
424
+ border-top: none;
425
+
426
+ grid-template-columns: v-bind(gridTemplateColumns);
427
+
428
+ &--loading {
429
+ grid-template-columns: 1fr;
430
+ }
431
+
432
+ &--clickable {
433
+ text-decoration: none;
434
+
435
+ &:hover {
436
+ cursor: pointer;
437
+ background-color: $unnnic-color-neutral-lightest;
438
+ }
439
+ }
440
+
441
+ &:last-of-type {
442
+ border-radius: 0 0 $unnnic-border-radius-sm $unnnic-border-radius-sm;
443
+ }
444
+ }
445
+
446
+ &-cell {
447
+ @extend %base-cell;
448
+
449
+ &-text {
450
+ margin: 0;
451
+
452
+ overflow: hidden;
453
+ white-space: nowrap;
454
+ text-overflow: ellipsis;
455
+ }
456
+ }
457
+
458
+ td.unnnic-data-table__body-cell--sm {
459
+ padding: $unnnic-spacing-ant $unnnic-spacing-sm;
460
+ }
461
+
462
+ &-cell--loading {
463
+ margin: $unnnic-spacing-xl 0;
464
+ padding: 0;
465
+
466
+ width: 100%;
467
+
468
+ pointer-events: none;
469
+ }
470
+ }
471
+
472
+ %base-cell {
473
+ border-collapse: collapse;
474
+
475
+ padding: $unnnic-spacing-sm $unnnic-spacing-sm;
476
+
477
+ font-family: $unnnic-font-family-secondary;
478
+ font-size: $unnnic-font-size-body-gt;
479
+ line-height: $unnnic-line-height-small * 5.5;
480
+ text-align: left;
481
+ color: $unnnic-color-neutral-dark;
482
+
483
+ overflow: hidden;
484
+ white-space: nowrap;
485
+ text-overflow: ellipsis;
486
+ }
487
+
488
+ %base-row {
489
+ display: grid;
490
+ align-items: center;
491
+ }
492
+ }
493
+ </style>
@@ -9,7 +9,10 @@
9
9
  ref="dropdown-data"
10
10
  data-testid="dropdown-data"
11
11
  class="dropdown-data"
12
- :style="{ position: 'fixed', ...positions }"
12
+ :style="{
13
+ position: useAbsolutePosition ? 'absolute' : 'fixed',
14
+ ...positions,
15
+ }"
13
16
  >
14
17
  <slot
15
18
  name="inside"
@@ -34,6 +37,10 @@ export default {
34
37
  type: String,
35
38
  default: 'bottom-left',
36
39
  },
40
+ useAbsolutePosition: {
41
+ type: Boolean,
42
+ default: false,
43
+ },
37
44
  },
38
45
 
39
46
  data() {
@@ -118,9 +118,6 @@ export default {
118
118
  return !!this.$slots.label;
119
119
  },
120
120
  },
121
- methods: {
122
- fullySanitize,
123
- },
124
121
  watch: {
125
122
  val() {
126
123
  this.$emit('update:modelValue', fullySanitize(this.val));
@@ -132,6 +129,9 @@ export default {
132
129
  mounted() {
133
130
  this.val = this.modelValue;
134
131
  },
132
+ methods: {
133
+ fullySanitize,
134
+ },
135
135
  };
136
136
  </script>
137
137