@weni/unnnic-system 2.36.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.36.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>
@@ -87,6 +87,7 @@ import ModalDialog from './ModalDialog/ModalDialog.vue';
87
87
  import Tour from './Tour/Tour.vue';
88
88
  import Navigator from './Navigator/index.vue';
89
89
  import SelectTime from './SelectTime/index.vue';
90
+ import DataTable from './DataTable/index.vue';
90
91
 
91
92
  export const components = {
92
93
  unnnicFormElement: formElement,
@@ -179,6 +180,7 @@ export const components = {
179
180
  unnnicTour: Tour,
180
181
  unnnicNavigator: Navigator,
181
182
  unnnicSelectTime: SelectTime,
183
+ unnnicDataTable: DataTable,
182
184
  };
183
185
 
184
186
  export const unnnicFontSize = fontSize;
@@ -272,3 +274,4 @@ export const unnnicTableNext = TableNext;
272
274
  export const unnnicTour = Tour;
273
275
  export const unnnicNavigator = Navigator;
274
276
  export const unnnicSelectTime = SelectTime;
277
+ export const unnnicDataTable = DataTable;