@webitel/ui-sdk 25.10.10 → 25.10.12

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.
@@ -1,315 +1,318 @@
1
1
  <template>
2
- <div class="wt-table">
3
- <table
4
- class="wt-table__table"
5
- :class="{ 'wt-table__table--fixed-actions': fixedActions }"
2
+ <p-table
3
+ ref="table"
4
+ class="wt-table"
5
+ :value="data"
6
+ :show-headers="!headless"
7
+ :row-class="rowClass"
8
+ :row-style="rowStyle"
9
+ lazy
10
+ scrollable
11
+ scroll-height="flex"
12
+ @sort="sort"
13
+ @row-reorder="({dragIndex, dropIndex}) => emit('reorder:row', { oldIndex: dragIndex, newIndex: dropIndex })"
14
+ >
15
+ <p-column
16
+ v-if="rowReorder"
17
+ column-key="row-reorder"
18
+ row-reorder
19
+ header-style="width: 1%;"
6
20
  >
7
- <thead
8
- v-if="!headless"
9
- class="wt-table__head"
10
- >
11
- <tr
12
- :style="columnsStyle"
13
- class="wt-table__tr wt-table__tr__head"
14
- >
15
- <th
16
- v-if="selectable"
17
- class="wt-table__th wt-table__th--checkbox"
18
- >
19
- <wt-checkbox
20
- :selected="isAllSelected"
21
- @update:selected="selectAll"
22
- />
23
- </th>
24
- <th
25
- v-for="(col, key) of dataHeaders"
26
- :key="key"
27
- :class="[
28
- { 'wt-table__th--sortable': isColSortable(col) },
29
- `wt-table__th--sort-${col.sort}`,
30
- ]"
31
- class="wt-table__th"
32
- @click="sort(col)"
33
- >
34
- <div class="wt-table__th__text">
35
- {{ col.text }}
36
- </div>
37
- <wt-icon
38
- v-if="sortable"
39
- class="wt-table__th__sort-arrow wt-table__th__sort-arrow--asc"
40
- icon="sort-arrow-up"
41
- size="sm"
42
- />
43
- <wt-icon
44
- v-if="sortable"
45
- class="wt-table__th__sort-arrow wt-table__th__sort-arrow--desc"
46
- icon="sort-arrow-down"
47
- size="sm"
48
- />
49
- </th>
50
- <th
51
- v-if="gridActions"
52
- class="wt-table__th__actions"
53
- >
54
- <!-- @slot Table head actions row slot -->
55
- <slot name="actions-header" />
56
- </th>
57
- </tr>
58
- </thead>
59
-
60
- <tbody class="wt-table__body">
61
- <tr
62
- v-for="(row, dataKey) of data"
63
- :key="dataKey"
64
- :class="`wt-table__tr__${row.id || dataKey}`"
65
- :style="columnsStyle"
66
- class="wt-table__tr wt-table__tr__body"
21
+ <template #body="{ data: row }">
22
+ <wt-icon
23
+ v-if="!isRowReorderDisabled(row)"
24
+ icon="move"
25
+ data-pc-section="reorderablerowhandle"
26
+ />
27
+ </template>
28
+ </p-column>
29
+ <p-column
30
+ v-if="selectable"
31
+ column-key="row-select"
32
+ header-style="width: 1%;"
33
+ >
34
+ <template #header>
35
+ <wt-checkbox
36
+ :selected="isAllSelected"
37
+ @update:selected="selectAll"
38
+ />
39
+ </template>
40
+
41
+ <template #body="{ data: row }">
42
+ <!-- check if row exists to prevent rendering errors -->
43
+ <wt-checkbox
44
+ v-if="row"
45
+ :selected="_selected.includes(row)"
46
+ @update:selected="handleSelection(row, $event)"
47
+ />
48
+ </template>
49
+ </p-column>
50
+ <p-column
51
+ v-for="(col, idx) of dataHeaders"
52
+ :key="col.value"
53
+ :column-key="col.field"
54
+ :field="col.field"
55
+ :sortable="isColSortable(col)"
56
+ :hidden="isColumnHidden(col)"
57
+ >
58
+ <template #header>
59
+ <div style="width: 0;" class="wt-table__th__content">
60
+ {{ col.text }}
61
+ </div>
62
+ </template>
63
+
64
+ <template #body="{ data: row, index }">
65
+ <!--
66
+ @slot Customize data columns. Recommended for representing nested data structures like object or array, and adding specific elements like select or chip
67
+ @scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
68
+ -->
69
+ <div
70
+ :style="columnStyle(col)"
71
+ class="wt-table__td__content"
67
72
  >
68
- <td
69
- v-if="selectable"
70
- class="wt-table__td wt-table__td--checkbox"
71
- >
72
- <wt-checkbox
73
- :selected="_selected.includes(row)"
74
- @update:selected="handleSelection(row, $event)"
75
- />
76
- </td>
77
-
78
- <td
79
- v-for="(col, headerKey) of dataHeaders"
80
- :key="headerKey"
81
- class="wt-table__td"
82
- >
83
- <!--
84
- @slot Customize data columns. Recommended for representing nested data structures like object or array, and adding specific elements like select or chip
85
- @scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
86
- -->
87
- <slot
88
- :index="dataKey"
89
- :item="row"
90
- :name="col.value"
91
- >
92
- <div>{{ row[col.value] }}</div>
93
- </slot>
94
- </td>
95
-
96
- <td
97
- v-if="gridActions"
98
- class="wt-table__td__actions"
99
- >
100
- <!--
101
- @slot Table body actions row slot
102
- @scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
103
- -->
104
- <slot
105
- :index="dataKey"
106
- :item="row"
107
- name="actions"
108
- />
109
- </td>
110
- </tr>
111
- </tbody>
112
-
113
- <tfoot
114
- v-if="isTableFooter"
115
- class="wt-table__foot"
73
+ <!-- check if row exists (under certain conditions row can be missing, e.g., during async data loading)
74
+ this guard prevents rendering errors and keeps the table stable -->
75
+ <slot
76
+ v-if="row"
77
+ :index="index"
78
+ :item="row"
79
+ :name="col.value"
80
+ >{{ row[col.value] }}</slot>
81
+ </div>
82
+ </template>
83
+ <template #sorticon>
84
+ <wt-icon
85
+ v-if="col.sort === 'asc'"
86
+ class="wt-table__th__sort-arrow wt-table__th__sort-arrow--asc"
87
+ icon="sort-arrow-up"
88
+ size="sm"
89
+ />
90
+ <wt-icon
91
+ v-else-if="col.sort === 'desc'"
92
+ class="wt-table__th__sort-arrow wt-table__th__sort-arrow--desc"
93
+ icon="sort-arrow-down"
94
+ size="sm"
95
+ />
96
+ </template>
97
+ <template
98
+ v-if="isTableColumnFooters"
99
+ #footer
116
100
  >
117
- <tr
118
- :style="columnsStyle"
119
- class="wt-table__tr wt-table__tr__foot"
120
- >
121
- <!-- empty checkbox column -->
122
- <th
123
- v-if="selectable"
124
- class="wt-table__th__checkbox"
101
+ <!--
102
+ @slot Add your custom aggregations for column in table footer. Table footer is rendered conditionally depending on templates with "-footer" name
103
+ @scope [ { "name": "header", "description": "header object" } ]
104
+ -->
105
+ <slot :name="`${col.value}-footer`" />
106
+ </template>
107
+ </p-column>
108
+ <p-column
109
+ v-if="gridActions"
110
+ column-key="row-actions"
111
+ style="width: 112px;"
112
+ :frozen="fixedActions"
113
+ align-frozen="right"
114
+ >
115
+ <template #header>
116
+ <!-- @slot Table head actions row slot -->
117
+ <slot name="actions-header" />
118
+ </template>
119
+ <template #body="{data: actionsData, index}">
120
+ <!--
121
+ @slot Table body actions row slot
122
+ @scope [ { "name": "item", "description": "Data row object" }, { "name": "index", "description": "Data row index" } ]
123
+ -->
124
+ <div class="wt-table__td__actions">
125
+ <!-- check if row exists to prevent rendering errors -->
126
+ <slot
127
+ v-if="actionsData"
128
+ name="actions"
129
+ :index="index"
130
+ :item="actionsData"
125
131
  />
126
- <td
127
- v-for="(col, headerKey) of dataHeaders"
128
- :key="headerKey"
129
- class="wt-table__td"
130
- >
131
- <!--
132
- @slot Add your custom aggregations for column in table footer. Table footer is rendered conditionally depending on templates with "-footer" name
133
- @scope [ { "name": "header", "description": "header object" } ]
134
- -->
135
- <slot
136
- :header="col"
137
- :index="headerKey"
138
- :name="`${col.value}-footer`"
139
- />
140
- </td>
141
- </tr>
142
- </tfoot>
143
- </table>
144
- </div>
132
+ </div>
133
+ </template>
134
+ </p-column>
135
+ <template
136
+ v-if="isTableFooter"
137
+ #footer
138
+ >
139
+ <slot name="footer" />
140
+ </template>
141
+ </p-table>
145
142
  </template>
146
143
 
147
- <script>
148
- import { getNextSortOrder } from '../../scripts/sortQueryAdapters.js';
144
+ <script setup lang="ts">
145
+ import type { DataTableProps } from 'primevue';
146
+ import { computed, defineProps, ref, useSlots,useTemplateRef, withDefaults } from 'vue';
147
+ import { useI18n } from 'vue-i18n';
149
148
 
150
- export default {
151
- name: 'WtTable',
152
- props: {
153
- /**
154
- * 'Accepts list of header objects. Draws text depending on "text" property, looks for data values through "value", "show" boolean controls visibility of a column (if undefined, all visible by default). ' Column width is calculated by "width" param. By default, sets minmax(150px, 1fr). '
155
- */
156
- headers: {
157
- type: Array,
158
- default: () => [],
159
- },
160
- /**
161
- * 'List of data, represented by table. '
162
- */
163
- data: {
164
- type: Array,
165
- default: () => [],
166
- },
167
- /**
168
- * 'If true, draws sorting arrows and sends sorting events at header click. Draws a sorting arrow by "sort": "asc"/"desc" header value. '
169
- */
170
- sortable: {
171
- type: Boolean,
172
- default: false,
173
- },
174
- /**
175
- * 'If true, draws row selection checkboxes. Checkbox toggles data object _isSelected property. It's IMPORTANT to set this property before sending data to table. '
176
- */
177
- selectable: {
178
- type: Boolean,
179
- default: true,
180
- },
181
- selected: {
182
- type: Array,
183
- // no default! because we need to know if it's passed or not
184
- },
185
- /**
186
- * 'If true, reserves space for 3 icon actions in the last column. Accessible by "actions" slot. '
187
- */
188
- gridActions: {
189
- type: Boolean,
190
- default: true,
191
- },
192
- /**
193
- * 'If true, 3 icon actions in the last column have position:sticky and fixed on the right'
194
- */
195
- fixedActions: {
196
- type: Boolean,
197
- default: false,
198
- },
199
- /**
200
- * 'If true, displays table without header.'
201
- */
202
- headless: {
203
- type: Boolean,
204
- default: false,
205
- },
206
- },
207
- emits: ['sort', 'update:selected'],
149
+ import { getNextSortOrder } from '../../scripts/sortQueryAdapters.js';
150
+ import type { WtTableHeader } from './types/WtTable';
151
+
152
+ interface Props extends DataTableProps{
153
+ /**
154
+ * 'Accepts list of header objects. Draws text depending on "text" property, looks for data values through "value", "show" boolean controls visibility of a column (if undefined, all visible by default). ' Column width is calculated by "width" param. By default, sets 140px. '
155
+ */
156
+ headers?: WtTableHeader[];
157
+ /**
158
+ * 'List of data, represented by table. '
159
+ */
160
+ data?: Array<unknown>;
161
+ /**
162
+ * 'If true, draws sorting arrows and sends sorting events at header click. Draws a sorting arrow by "sort": "asc"/"desc" header value. '
163
+ */
164
+ sortable?: boolean;
165
+ /**
166
+ * 'If true, draws row selection checkboxes. Checkbox toggles data object _isSelected property. It's IMPORTANT to set this property before sending data to table. '
167
+ */
168
+ selectable?: boolean;
169
+ selected?: Array<unknown>;
170
+ /**
171
+ * 'If true, reserves space for 3 icon actions in the last column. Accessible by "actions" slot. '
172
+ */
173
+ gridActions?: boolean;
174
+ /**
175
+ * 'If true, 3 icon actions in the last column have position:sticky and fixed on the right'
176
+ */
177
+ fixedActions?: boolean;
178
+ /**
179
+ * 'If true, displays table without header.'
180
+ */
181
+ headless?: boolean;
182
+ /**
183
+ * 'If true, allows to reorder rows.'
184
+ */
185
+ rowReorder?: boolean;
186
+ /**
187
+ * 'If true, restrict sprecific row reorder.'
188
+ */
189
+ isRowReorderDisabled?: (row) => boolean;
190
+ rowClass?: () => string;
191
+ rowStyle?: () => { [key: string]: string };
192
+ }
208
193
 
209
- data: () => ({}),
194
+ const props = withDefaults(defineProps<Props>(), {
195
+ headers: () => [],
196
+ data: () => [],
197
+ sortable: false,
198
+ selectable: true,
199
+ gridActions: true,
200
+ fixedActions: false,
201
+ headless: false,
202
+ rowReorder: false,
203
+ isRowReorderDisabled: () => false,
204
+ rowClass: () => '',
205
+ rowStyle: () => ({}),
206
+ });
207
+
208
+ const { t } = useI18n();
209
+
210
+ const slots = useSlots();
211
+
212
+ const emit = defineEmits(['sort', 'update:selected', 'reorder:row']);
213
+
214
+ const table = useTemplateRef('table');
215
+
216
+ const _selected = computed(() => {
217
+ // _isSelected for backwards compatibility
218
+ return props.selectable
219
+ ? props.selected || props.data.filter(item => item._isSelected)
220
+ : [];
221
+ });
222
+
223
+ const dataHeaders = computed(() => {
224
+ return props.headers
225
+ .map(header => {
226
+ if (!header.text && header.locale) {
227
+ return {
228
+ ...header,
229
+ text: typeof header.locale === 'string' ? t(header.locale) : t(...header.locale),
230
+ };
231
+ }
232
+ return header;
233
+ });
234
+ });
210
235
 
211
- computed: {
212
- _selected() {
213
- // _isSelected for backwards compatibility
214
- return this.selected || this.data.filter((item) => item._isSelected);
215
- },
236
+ const isColumnHidden = (col) => {
237
+ return col.show === false
238
+ }
216
239
 
217
- isAllSelected() {
218
- return this._selected.length === this.data.length && this.data.length > 0;
219
- },
240
+ const columnStyle = (col) => {
241
+ const baseWidth = 140
220
242
 
221
- dataHeaders() {
222
- return this.headers
223
- .filter((header) => header.show === undefined || header.show)
224
- .map((header) => {
225
- if (!header.text && header.locale) {
226
- return {
227
- ...header,
228
- text:
229
- typeof header.locale === 'string'
230
- ? this.$t(header.locale)
231
- : this.$t(...header.locale),
232
- };
233
- }
234
- return header;
235
- });
236
- },
243
+ return {
244
+ minWidth: col.width || `${baseWidth}px`,
245
+ }
246
+ }
237
247
 
238
- columnsStyle() {
239
- let gridTemplateColumns = '';
240
- if (this.selectable) gridTemplateColumns += '24px '; // checkbox
248
+ const isTableColumnFooters = computed(() => {
249
+ return Object.keys(slots).some(slotName => slotName.includes('-footer'));
250
+ });
241
251
 
242
- const defaultColumnWidth = 'minmax(140px, 1fr)';
243
- this.dataHeaders.forEach((header) => {
244
- gridTemplateColumns += ` ${(header.width || defaultColumnWidth).trim()}`;
245
- });
252
+ const isTableFooter = computed(() => {
253
+ return Object.keys(slots).some(slotName => slotName === 'footer');
254
+ });
246
255
 
247
- if (this.gridActions) gridTemplateColumns += ` ${'112px'}`; // actions
248
- return `grid-template-columns: ${gridTemplateColumns}`;
249
- },
256
+ const isAllSelected = computed(() => {
257
+ return _selected.value.length === props.data.length && props.data.length > 0;
258
+ })
250
259
 
251
- isTableFooter() {
252
- return Object.keys(this.$slots).some((slotName) =>
253
- slotName.includes('-footer'),
254
- );
255
- },
256
- },
260
+ const sort = ({sortField}) => {
261
+ const col = dataHeaders.value.find(header => header.value === sortField)
262
+ if (!isColSortable(col)) return;
263
+ const nextSort = getNextSortOrder(col.sort);
264
+ emit('sort', col, nextSort);
265
+ }
257
266
 
258
- methods: {
259
- sort(col) {
260
- if (!this.isColSortable(col)) return;
261
- const nextSort = getNextSortOrder(col.sort);
262
- this.$emit('sort', col, nextSort);
263
- },
264
- isColSortable({ sort }) {
265
- /* --sortable = sortable && col.sort === undefined cause there may be some columns we don't want to sort
267
+ const isColSortable = ({ sort }) => {
268
+ /* --sortable = sortable && col.sort === undefined cause there may be some columns we don't want to sort
266
269
  strict check for === undefined is used because col.sort = null is sort order too (actualu, without sort)
267
270
  so we need to check if this property is present
268
271
  */
269
- return this.sortable && sort !== undefined;
270
- },
271
- selectAll() {
272
- if (this.selected) {
273
- if (this.isAllSelected) {
274
- this.$emit('update:selected', []);
275
- } else {
276
- this.$emit('update:selected', [...this.data]);
277
- }
278
- } else {
279
- // for backwards compatibility
272
+ return props.sortable && sort !== undefined;
273
+ }
280
274
 
281
- // https://webitel.atlassian.net/browse/WTEL-4634
282
- // Value for _isSelected must be assigned explicitly.
283
- // Because allSelected recomputes after each change
275
+ const selectAll = () => {
276
+ if (props.selected) {
277
+ if (isAllSelected.value) {
278
+ emit('update:selected', []);
279
+ } else {
280
+ emit('update:selected', [...props.data]);
281
+ }
282
+ } else {
283
+ // for backwards compatibility
284
284
 
285
- if (this.isAllSelected) {
286
- this.data.forEach((item) => {
287
- item._isSelected = false;
288
- });
289
- } else {
290
- this.data.forEach((item) => {
291
- item._isSelected = true;
292
- });
293
- }
294
- }
295
- },
296
- handleSelection(row, select) {
297
- if (this.selected) {
298
- if (select) {
299
- this.$emit('update:selected', [...this._selected, row]);
300
- } else {
301
- this.$emit(
302
- 'update:selected',
303
- this._selected.filter((item) => item !== row),
304
- );
305
- }
306
- } else {
307
- // for backwards compatibility
308
- row._isSelected = !row._isSelected;
309
- }
310
- },
311
- },
312
- };
285
+ // https://webitel.atlassian.net/browse/WTEL-4634
286
+ // Value for _isSelected must be assigned explicitly.
287
+ // Because allSelected recomputes after each change
288
+
289
+ if (isAllSelected.value) {
290
+ props.data.forEach((item) => {
291
+ item._isSelected = false;
292
+ });
293
+ } else {
294
+ props.data.forEach((item) => {
295
+ item._isSelected = true;
296
+ });
297
+ }
298
+ }
299
+ }
300
+
301
+ const handleSelection = (row, select) => {
302
+ if (props.selected) {
303
+ if (select) {
304
+ emit('update:selected', [..._selected.value, row]);
305
+ } else {
306
+ emit(
307
+ 'update:selected',
308
+ _selected.value.filter((item) => item !== row),
309
+ );
310
+ }
311
+ } else {
312
+ // for backwards compatibility
313
+ row._isSelected = !row._isSelected;
314
+ }
315
+ }
313
316
  </script>
314
317
 
315
318
  <style lang="scss">
@@ -317,105 +320,31 @@ export default {
317
320
  </style>
318
321
 
319
322
  <style lang="scss" scoped>
320
- @use '@webitel/styleguide/typography' as *;
321
323
  @use '@webitel/styleguide/scroll' as *;
324
+ @use '@webitel/styleguide/typography' as *;
322
325
 
323
326
  .wt-table {
324
327
  @extend %wt-scrollbar;
325
328
  overflow: auto;
326
329
  }
327
330
 
328
- .wt-table__table {
329
- border-collapse: collapse;
330
- width: 100%;
331
-
332
- &--fixed-actions {
333
- // make action icons fixed to right
334
-
335
- .wt-table__tr {
336
- .wt-table__td__actions {
337
- position: sticky;
338
- right: 0;
339
- background: var(--content-wrapper-color);
340
- }
341
-
342
- &:nth-child(2n) {
343
- .wt-table__td__actions {
344
- background: var(--wt-table-zebra-color);
345
- }
346
- }
347
- }
348
- }
349
- }
350
-
351
- .wt-table__tr {
352
- display: grid;
353
- grid-template-columns: repeat(auto-fit, var(--table-col-min-width));
354
- transition: var(--transition);
355
- background: var(--wt-table-primary-color);
356
- padding: var(--table-row-padding);
357
- grid-column-gap: var(--table-column-gap);
358
-
359
- &:nth-child(2n) {
360
- background: var(--wt-table-zebra-color);
361
- }
331
+ .wt-table :deep(.p-datatable-table-container) {
332
+ @extend %wt-scrollbar;
362
333
  }
363
334
 
364
- .wt-table__tr__head {
365
- border: var(--table-head-border);
366
- border-color: var(--wt-table-head-border-color);
367
- border-radius: var(--border-radius);
368
- background: var(--wt-table-head-background-color);
335
+ .wt-table__th__content {
336
+ @extend %typo-body-1-bold;
337
+ white-space: nowrap;
369
338
  }
370
339
 
371
- .wt-table__th,
372
- .wt-table__td {
340
+ .wt-table__td__content {
373
341
  @extend %typo-body-1;
374
- display: flex;
375
- align-items: center;
376
- padding: 0;
377
- width: 100%;
378
- max-width: 100%;
379
- height: fit-content;
380
- min-height: var(--table-min-height);
381
- word-break: break-all;
382
- overflow-wrap: break-word;
383
-
384
- &__actions {
385
- display: flex;
386
- justify-content: flex-end;
387
- align-items: flex-start;
388
- gap: var(--spacing-xs);
389
- }
390
- }
391
-
392
- .wt-table__th {
393
- font-weight: normal;
394
-
395
- &--sortable {
396
- cursor: pointer;
397
-
398
- &:hover :deep(.wt-icon) {
399
- fill: var(--icon-active-color);
400
- }
401
- }
402
-
403
- .wt-table__th__sort-arrow {
404
- display: none;
405
- margin-left: var(--table-head-sort-arrow-margin);
406
- }
407
-
408
- &--sort-asc .wt-table__th__sort-arrow--asc {
409
- display: block;
410
- }
411
-
412
- &--sort-desc .wt-table__th__sort-arrow--desc {
413
- display: block;
414
- }
415
342
  }
416
343
 
417
- .wt-table__foot {
418
- border-color: var(--wt-table-head-border-color);
419
- border-top: var(--wt-table-head-border-color);
344
+ .wt-table__td__actions {
345
+ display: flex;
346
+ justify-content: flex-end;
347
+ align-items: flex-start;
348
+ gap: var(--spacing-xs);
420
349
  }
421
- </style>
350
+ </style>