@kubex/zinc 1.1.121 → 1.1.123

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 (37) hide show
  1. package/dist/custom-elements.json +2847 -1816
  2. package/dist/vscode.html-custom-data.json +189 -167
  3. package/dist/web-types.json +531 -465
  4. package/dist/zn.d.ts +401 -296
  5. package/dist/zn.min.js +1478 -1337
  6. package/docs/pages/components/button.md +9 -0
  7. package/docs/pages/components/data-table-filter.md +60 -4
  8. package/docs/pages/components/data-table-search.md +3 -3
  9. package/docs/pages/components/data-table.md +78 -9
  10. package/docs/pages/components/datepicker.md +10 -0
  11. package/docs/pages/components/menu-item.md +16 -2
  12. package/package.json +1 -1
  13. package/src/components/button/button.component.ts +11 -4
  14. package/src/components/button/button.scss +12 -1
  15. package/src/components/data-table/data-table.component.ts +341 -139
  16. package/src/components/data-table/data-table.scss +110 -43
  17. package/src/components/data-table/data-table.test.ts +110 -0
  18. package/src/components/data-table-filter/data-table-filter.component.ts +487 -49
  19. package/src/components/data-table-filter/data-table-filter.scss +163 -9
  20. package/src/components/data-table-filter/data-table-filter.test.ts +328 -2
  21. package/src/components/data-table-search/data-table-search.component.ts +1 -1
  22. package/src/components/data-table-search/data-table-search.scss +5 -0
  23. package/src/components/datepicker/datepicker.component.ts +23 -2
  24. package/src/components/datepicker/datepicker.scss +25 -0
  25. package/src/components/datepicker/datepicker.test.ts +28 -1
  26. package/src/components/input/input.component.ts +1 -0
  27. package/src/components/input/input.scss +1 -1
  28. package/src/components/menu/menu.component.ts +2 -0
  29. package/src/components/menu/menu.scss +23 -2
  30. package/src/components/menu-item/menu-item.component.ts +7 -1
  31. package/src/components/menu-item/menu-item.scss +4 -4
  32. package/src/components/menu-item/menu-item.test.ts +39 -1
  33. package/src/components/panel/panel.component.ts +12 -5
  34. package/src/components/panel/panel.scss +3 -3
  35. package/src/components/query-builder/query-builder.component.ts +1 -1
  36. package/src/components/tooltip/tooltip.component.ts +2 -2
  37. package/src/components/tooltip/tooltip.test.ts +70 -2
@@ -5,8 +5,14 @@
5
5
  @include wc.scrollbars;
6
6
  }
7
7
 
8
- .table-container {
9
- overflow: auto;
8
+ // Separates a top-level filter bar from the table's own panel below it
9
+ slot[name="filter-top"]::slotted(*) {
10
+ margin-bottom: var(--zn-spacing-small);
11
+ }
12
+
13
+ .table__scroll {
14
+ overflow-x: auto;
15
+ @include wc.scrollbars;
10
16
  }
11
17
 
12
18
  table {
@@ -28,17 +34,6 @@ table {
28
34
  background-color: rgb(var(--zn-shadow));
29
35
  }
30
36
 
31
- &.table--standalone {
32
- background-color: rgb(var(--zn-panel));
33
- border-radius: var(--zn-border-radius);
34
- border: 1px solid rgb(var(--zn-border-color));
35
- overflow: hidden;
36
-
37
- thead tr th {
38
- border-top: 0;
39
- }
40
- }
41
-
42
37
  thead {
43
38
  color: rgb(var(--zn-text-heading));
44
39
  font-weight: var(--zn-font-weight-medium);
@@ -232,32 +227,69 @@ table {
232
227
  display: flex;
233
228
  flex-direction: row;
234
229
  justify-content: space-between;
235
- align-items: baseline;
236
- padding-block: var(--zn-spacing-small);
230
+ align-items: flex-start;
231
+ padding: var(--zn-spacing-x-small) var(--zn-spacing-medium);
237
232
  gap: var(--zn-spacing-small);
238
233
 
239
234
  &__actions,
240
235
  &__query-builder {
241
236
  display: flex;
242
237
  flex-direction: row;
243
- align-items: baseline;
238
+ align-items: center;
244
239
  gap: var(--zn-spacing-small);
245
240
  }
246
241
 
242
+ &__toggle--active::part(base) {
243
+ background-color: rgb(var(--zn-color-tab));
244
+ }
245
+
246
+ &__caption {
247
+ @include wc.text-style(h1);
248
+
249
+ // Sits on the same 36px line as the controls opposite it
250
+ line-height: 36px;
251
+ margin: 0;
252
+ overflow: hidden;
253
+ white-space: nowrap;
254
+ text-overflow: ellipsis;
255
+ }
256
+
257
+ // Controls are a uniform 36px, so topping them out keeps them on one line while
258
+ // a search's help text grows the header downwards
247
259
  &__right {
248
260
  display: flex;
249
261
  flex-direction: row;
250
- align-items: center;
262
+ align-items: flex-start;
251
263
  gap: var(--zn-spacing-x-small);
252
264
  margin-left: auto;
253
265
  }
254
266
  }
255
267
 
268
+ &__filters {
269
+ padding: var(--zn-spacing-x-small) var(--zn-spacing-medium);
270
+
271
+ &[hidden] {
272
+ display: none;
273
+ }
274
+ }
275
+
256
276
  &__footer {
257
277
  display: flex;
258
278
  flex-direction: row;
259
279
  justify-content: space-between;
260
- padding: var(--zn-spacing-small) 0;
280
+ flex-wrap: wrap;
281
+ gap: var(--zn-spacing-small);
282
+ padding: var(--zn-spacing-x-small) var(--zn-spacing-medium);
283
+
284
+ // The joined control must never break across lines
285
+ zn-button-group::part(base) {
286
+ flex-wrap: nowrap;
287
+ }
288
+
289
+ // zn-panel's footer supplies its own padding
290
+ &--panel {
291
+ padding: 0;
292
+ }
261
293
 
262
294
  &__right, &__left {
263
295
  display: flex;
@@ -270,40 +302,49 @@ table {
270
302
  display: flex;
271
303
  flex-direction: row;
272
304
  align-items: center;
273
- opacity: 0.8;
274
305
  gap: var(--zn-spacing-x-small);
275
306
 
307
+ p {
308
+ margin: 0;
309
+ color: rgb(var(--zn-text));
310
+ opacity: 0.7;
311
+ white-space: nowrap;
312
+ }
313
+
276
314
  zn-select[name="rowPerPage"] {
277
- max-width: 75px;
315
+ --zn-input-background-color: rgb(var(--zn-panel));
316
+
317
+ width: 66px;
278
318
  }
279
319
 
320
+ // Matches the height of the pagination control it sits beside
280
321
  zn-select[name="rowPerPage"]::part(combobox) {
281
- padding-inline-end: 8px;
322
+ min-height: 36px;
323
+ padding-inline: var(--zn-spacing-x-small);
282
324
  }
283
325
 
284
- zn-select[name="rowPerPage"]::part(expand-icon) {
326
+ zn-select[name="rowPerPage"]::part(expand-icon) {
285
327
  margin-inline-start: 0;
286
328
  }
287
329
  }
288
330
 
289
- &__pagination-buttons {
290
- display: flex;
291
- flex-direction: row;
292
- align-items: center;
293
- gap: var(--zn-spacing-x-small);
331
+ // Sized to match the icon-button arrows it is joined to, but free to grow for large page numbers
332
+ &__pagination-page::part(base) {
333
+ height: 36px;
334
+ min-height: 36px;
335
+ max-height: 36px;
336
+ min-width: 32px;
337
+ padding: 0 var(--zn-spacing-2x-small);
338
+ justify-content: center;
339
+ border-color: rgb(var(--zn-border-color));
340
+ color: rgb(var(--zn-text));
341
+ font-weight: var(--zn-font-weight-normal);
342
+ white-space: nowrap;
294
343
  }
295
344
 
296
345
  &__pagination-page--active::part(base) {
297
346
  background-color: var(--zn-color-neutral-300);
298
347
  border-color: var(--zn-color-neutral-300);
299
- color: rgb(var(--zn-text));
300
- }
301
-
302
- &__pagination-ellipsis {
303
- display: flex;
304
- align-items: center;
305
- padding: 0 var(--zn-spacing-x-small);
306
- color: rgb(var(--zn-text));
307
348
  }
308
349
  }
309
350
 
@@ -330,6 +371,30 @@ table {
330
371
  }
331
372
  }
332
373
 
374
+ zn-menu {
375
+ --zn-menu-min-width: var(--zn-size-spanel);
376
+ }
377
+
378
+ // A table's header and footer are chrome around the rows, so they sit tighter than
379
+ // the panel's own content padding. The header's trailing edge tucks in further still,
380
+ // since the controls there are already inset by their own button padding.
381
+ zn-panel {
382
+ --zn-panel-header-padding: var(--zn-spacing-x-small) var(--zn-spacing-x-small)
383
+ var(--zn-spacing-x-small) var(--zn-spacing-medium);
384
+ --zn-panel-footer-padding: var(--zn-spacing-x-small) var(--zn-spacing-medium);
385
+ }
386
+
387
+ // Closes the rows off from the footer, which the panel border would otherwise do
388
+ zn-panel::part(footer) {
389
+ border-top: 1px solid rgb(var(--zn-border-color));
390
+ }
391
+
392
+ // Dropdowns sit over dense rows here, so they need a solid surface rather than
393
+ // zn-menu's translucent default
394
+ zn-menu::part(base) {
395
+ background-color: rgb(var(--zn-panel));
396
+ }
397
+
333
398
  .reduced-opacity .table tbody {
334
399
  opacity: 0.5;
335
400
  }
@@ -417,16 +482,18 @@ table:has(tbody tr:first-of-type.table__row--selected) thead tr th {
417
482
  border-bottom-color: var(--zn-color-purple-600) !important;
418
483
  }
419
484
 
420
- .table-group__title {
485
+ tbody tr.table__row--group th {
486
+ padding: var(--zn-spacing-small) var(--zn-spacing-medium);
487
+ text-align: left;
488
+ white-space: nowrap;
489
+ background-color: rgb(var(--zn-panel));
490
+ color: rgba(var(--zn-text-panel-title), 100%);
421
491
  font-weight: var(--zn-font-weight-semibold);
422
492
  font-size: var(--zn-font-size-large);
423
- color: rgba(var(--zn-text-panel-title), 100%);
424
- line-height: var(--zn-line-height-looser);
425
- padding-left: var(--zn-spacing-medium);
426
- margin: 0;
427
- overflow: hidden;
428
- white-space: nowrap;
429
- text-overflow: ellipsis;
493
+ }
494
+
495
+ tbody tr.table__row--group:not(:first-of-type) th {
496
+ border-top: 1px solid rgb(var(--zn-border-color));
430
497
  }
431
498
 
432
499
  tbody tr.table__row--error td {
@@ -10,6 +10,116 @@ describe('<zn-data-table>', () => {
10
10
  expect(el).to.exist;
11
11
  });
12
12
 
13
+ // filter-top renders above the panel, so it must not bring an otherwise empty header row with it
14
+ it('renders no header when only filter-top is slotted', async () => {
15
+ const el = await fixture<ZnDataTable>(html`
16
+ <zn-data-table standalone no-initial-load>
17
+ <div slot="filter-top">Filters</div>
18
+ <div slot="empty-state">Nothing yet</div>
19
+ </zn-data-table>`);
20
+ await el.updateComplete;
21
+
22
+ expect(el.shadowRoot!.querySelector('.table__header__right')).to.not.exist;
23
+ expect(el.shadowRoot!.querySelector('zn-header')).to.not.exist;
24
+ });
25
+
26
+ it('leaves a bare empty state unwrapped by the standalone panel', async () => {
27
+ const el = await fixture<ZnDataTable>(html`
28
+ <zn-data-table standalone no-initial-load>
29
+ <div slot="empty-state">Nothing yet</div>
30
+ </zn-data-table>`);
31
+ await el.updateComplete;
32
+
33
+ expect(el.shadowRoot!.querySelector('zn-panel')).to.not.exist;
34
+ expect(el.shadowRoot!.querySelector('slot[name="empty-state"]')).to.exist;
35
+ });
36
+
37
+ it('keeps the standalone panel for an empty state that has a caption', async () => {
38
+ const el = await fixture<ZnDataTable>(html`
39
+ <zn-data-table standalone no-initial-load caption="Customers">
40
+ <div slot="empty-state">Nothing yet</div>
41
+ </zn-data-table>`);
42
+ await el.updateComplete;
43
+
44
+ expect(el.shadowRoot!.querySelector('zn-panel')).to.exist;
45
+ });
46
+
47
+ // Raising the page size past the total used to drop the whole footer, leaving no way back
48
+ it('keeps the rows-per-page selector when the page size covers every row', async () => {
49
+ const originalFetch = window.fetch;
50
+ window.fetch = () => Promise.resolve(new Response(JSON.stringify({
51
+ rows: Array.from({length: 24}, (_, i) => ({id: String(i), cells: [{text: 'Row', column: 'name'}]})),
52
+ page: 1,
53
+ perPage: 50,
54
+ total: 24,
55
+ }), {status: 200, headers: {'Content-Type': 'application/json'}}));
56
+
57
+ try {
58
+ const el = await fixture<ZnDataTable>(html`
59
+ <zn-data-table data-uri="/test-data" headers='{"name": {"key": "name", "label": "Name"}}'></zn-data-table>`);
60
+ await waitUntil(() => el.shadowRoot!.querySelector('tbody tr.table__row--data'));
61
+
62
+ expect(el.shadowRoot!.querySelector('zn-select[name="rowPerPage"]')).to.exist;
63
+ expect(el.shadowRoot!.querySelector('.table__footer__pagination-page')).to.not.exist;
64
+ } finally {
65
+ window.fetch = originalFetch;
66
+ }
67
+ });
68
+
69
+ it('hides the rows-per-page selector when the dataset fits the smallest page size', async () => {
70
+ const originalFetch = window.fetch;
71
+ window.fetch = () => Promise.resolve(new Response(JSON.stringify({
72
+ rows: [{id: '1', cells: [{text: 'Row', column: 'name'}]}],
73
+ page: 1,
74
+ perPage: 10,
75
+ total: 1,
76
+ }), {status: 200, headers: {'Content-Type': 'application/json'}}));
77
+
78
+ try {
79
+ const el = await fixture<ZnDataTable>(html`
80
+ <zn-data-table data-uri="/test-data" headers='{"name": {"key": "name", "label": "Name"}}'></zn-data-table>`);
81
+ await waitUntil(() => el.shadowRoot!.querySelector('tbody tr.table__row--data'));
82
+
83
+ expect(el.shadowRoot!.querySelector('zn-select[name="rowPerPage"]')).to.not.exist;
84
+ expect(el.shadowRoot!.querySelector('.table__footer')).to.not.exist;
85
+ } finally {
86
+ window.fetch = originalFetch;
87
+ }
88
+ });
89
+
90
+ // One table per group meant two scrollbars and columns that did not line up between groups
91
+ it('renders grouped rows as group header rows inside a single table', async () => {
92
+ const originalFetch = window.fetch;
93
+ const row = (id: string, status: string) => ({
94
+ id,
95
+ cells: [{text: 'Card ' + id, column: 'name'}, {text: status, column: 'status'}],
96
+ });
97
+ window.fetch = () => Promise.resolve(new Response(JSON.stringify({
98
+ rows: [row('1', 'active'), row('2', 'active'), row('3', 'archived')],
99
+ page: 1,
100
+ perPage: 10,
101
+ total: 3,
102
+ }), {status: 200, headers: {'Content-Type': 'application/json'}}));
103
+
104
+ try {
105
+ const el = await fixture<ZnDataTable>(html`
106
+ <zn-data-table data-uri="/test-data"
107
+ group-by="status"
108
+ groups="active,archived"
109
+ headers='{"name": {"key": "name", "label": "Name"}, "status": {"key": "status", "label": "Status", "hideColumn": true}}'></zn-data-table>`);
110
+ await waitUntil(() => el.shadowRoot!.querySelector('tbody tr.table__row--data'));
111
+
112
+ const root = el.shadowRoot!;
113
+ expect(root.querySelectorAll('table')).to.have.length(1);
114
+ expect(root.querySelectorAll('.table__scroll')).to.have.length(1);
115
+ expect([...root.querySelectorAll('tr.table__row--group')].map(r => r.textContent!.trim()))
116
+ .to.deep.equal(['Active', 'Archived']);
117
+ expect(root.querySelectorAll('tbody tr.table__row--data')).to.have.length(3);
118
+ } finally {
119
+ window.fetch = originalFetch;
120
+ }
121
+ });
122
+
13
123
  it('does not throw when updating selection without a select-all button', async () => {
14
124
  const el = await fixture<ZnDataTable>(html` <zn-data-table></zn-data-table> `);
15
125