@kubex/zinc 1.1.96 → 1.1.98

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 (41) hide show
  1. package/dist/chunks/zn.R2EAU6OS.js +1 -0
  2. package/dist/custom-elements.json +1422 -61
  3. package/dist/vscode.html-custom-data.json +188 -2
  4. package/dist/web-types.json +462 -5
  5. package/dist/zn.d.ts +326 -2
  6. package/dist/zn.min.js +594 -464
  7. package/docs/pages/components/button.md +11 -1
  8. package/docs/pages/components/chart.md +165 -3
  9. package/docs/pages/components/icon-picker.md +8 -0
  10. package/docs/pages/components/thumbnail-group.md +216 -0
  11. package/docs/pages/components/thumbnail.md +349 -0
  12. package/docs/pages/components/well.md +34 -0
  13. package/package.json +1 -1
  14. package/src/components/button/button.component.ts +8 -0
  15. package/src/components/chart/builders.test.ts +190 -1
  16. package/src/components/chart/builders.ts +246 -4
  17. package/src/components/chart/chart.component.ts +33 -1
  18. package/src/components/chart/chart.test.ts +66 -1
  19. package/src/components/chart/echarts-loader.ts +10 -0
  20. package/src/components/collapsible/collapsible.component.ts +49 -4
  21. package/src/components/collapsible/collapsible.scss +33 -14
  22. package/src/components/data-table/data-table.component.ts +13 -8
  23. package/src/components/data-table/data-table.scss +1 -1
  24. package/src/components/header/header.scss +1 -1
  25. package/src/components/icon-picker/icon-picker.component.ts +3 -0
  26. package/src/components/icon-picker/lucide-icons.ts +1756 -0
  27. package/src/components/page/page.scss +1 -1
  28. package/src/components/remarkd-editor/remarkd-editor.test.ts +39 -0
  29. package/src/components/split-pane/split-pane.scss +21 -15
  30. package/src/components/thumbnail/index.ts +12 -0
  31. package/src/components/thumbnail/thumbnail.component.ts +460 -0
  32. package/src/components/thumbnail/thumbnail.scss +360 -0
  33. package/src/components/thumbnail/thumbnail.test.ts +379 -0
  34. package/src/components/thumbnail-group/index.ts +12 -0
  35. package/src/components/thumbnail-group/thumbnail-group.component.ts +268 -0
  36. package/src/components/thumbnail-group/thumbnail-group.scss +87 -0
  37. package/src/components/thumbnail-group/thumbnail-group.test.ts +151 -0
  38. package/src/components/well/well.component.ts +22 -4
  39. package/src/components/well/well.scss +24 -0
  40. package/src/components/well/well.test.ts +43 -0
  41. package/src/zinc.ts +2 -0
@@ -1,7 +1,20 @@
1
1
  // src/components/chart/builders.ts
2
2
  import type { EChartsOption } from 'echarts';
3
3
 
4
- export type ChartType = 'area' | 'bar' | 'line' | 'sankey';
4
+ export type ChartType =
5
+ | 'area'
6
+ | 'bar'
7
+ | 'donut'
8
+ | 'funnel'
9
+ | 'gauge'
10
+ | 'heatmap'
11
+ | 'line'
12
+ | 'pie'
13
+ | 'radar'
14
+ | 'sankey'
15
+ | 'scatter'
16
+ | 'sunburst'
17
+ | 'treemap';
5
18
 
6
19
  export interface SeriesItem {
7
20
  name: string;
@@ -15,6 +28,20 @@ export interface SankeyEdge {
15
28
  value: number;
16
29
  }
17
30
 
31
+ export interface PieDataItem {
32
+ name?: string;
33
+ value: number;
34
+ color?: string;
35
+ itemStyle?: Record<string, unknown>;
36
+ }
37
+
38
+ export interface RadarIndicator {
39
+ name: string;
40
+ max?: number;
41
+ min?: number;
42
+ color?: string;
43
+ }
44
+
18
45
  export interface BuilderProps {
19
46
  type: ChartType;
20
47
  data: SeriesItem[];
@@ -30,9 +57,16 @@ export interface BuilderProps {
30
57
  scale?: boolean | number;
31
58
  textColor?: string;
32
59
  borderColor?: string;
60
+ innerRadius?: number | string;
61
+ outerRadius?: number | string;
62
+ showLabels?: boolean;
63
+ yCategories?: string[];
64
+ indicators?: RadarIndicator[];
65
+ minValue?: number;
66
+ maxValue?: number;
33
67
  }
34
68
 
35
- function commonOption(props: BuilderProps): EChartsOption {
69
+ function baseOption(props: BuilderProps, tooltipTrigger: 'axis' | 'item'): EChartsOption {
36
70
  const fallback = props.theme === 'dark' ? 'rgb(161, 161, 170)' : 'rgb(113, 113, 122)';
37
71
  const textColor = props.textColor ?? fallback;
38
72
  const animEnabled = props.enableAnimations !== false && props.enableAnimations !== 0;
@@ -44,7 +78,7 @@ function commonOption(props: BuilderProps): EChartsOption {
44
78
  ...(props.colors ? { color: props.colors } : {}),
45
79
  textStyle: { color: textColor },
46
80
  tooltip: {
47
- trigger: 'axis',
81
+ trigger: tooltipTrigger,
48
82
  appendTo: 'body',
49
83
  valueFormatter: props.yAxisAppend
50
84
  ? (v: number | string) => `${v}${props.yAxisAppend}`
@@ -56,6 +90,12 @@ function commonOption(props: BuilderProps): EChartsOption {
56
90
  icon: 'circle',
57
91
  textStyle: { color: textColor },
58
92
  },
93
+ };
94
+ }
95
+
96
+ function commonOption(props: BuilderProps): EChartsOption {
97
+ return {
98
+ ...baseOption(props, 'axis'),
59
99
  grid: {
60
100
  left: 40,
61
101
  right: 20,
@@ -120,7 +160,7 @@ function normalizeData(data: any[]): any[] {
120
160
 
121
161
  function seriesFromProps(
122
162
  props: BuilderProps,
123
- seriesType: 'bar' | 'line',
163
+ seriesType: 'bar' | 'line' | 'scatter',
124
164
  extra: (s: SeriesItem) => Record<string, unknown> = () => ({}),
125
165
  ) {
126
166
  return props.data.map((s) => ({
@@ -170,6 +210,208 @@ export function buildAreaOption(props: BuilderProps): EChartsOption {
170
210
  };
171
211
  }
172
212
 
213
+ export function buildScatterOption(props: BuilderProps): EChartsOption {
214
+ return {
215
+ ...commonOption(props),
216
+ xAxis: buildXAxis({ ...props, xAxisType: props.xAxisType ?? 'numeric' }),
217
+ yAxis: buildYAxis(props),
218
+ series: seriesFromProps(props, 'scatter', () => ({
219
+ symbolSize: props.datapointSize,
220
+ })),
221
+ };
222
+ }
223
+
224
+ function normalizePieData(data: any[], categories: string[]): PieDataItem[] {
225
+ return data.map((item: number | PieDataItem, index) => {
226
+ if (typeof item === 'number') {
227
+ return { name: categories[index] ?? `${index + 1}`, value: item };
228
+ }
229
+
230
+ const { color, itemStyle, ...rest } = item;
231
+ return {
232
+ ...rest,
233
+ name: item.name ?? categories[index] ?? `${index + 1}`,
234
+ ...(color ? { itemStyle: { ...itemStyle, color } } : itemStyle ? { itemStyle } : {}),
235
+ };
236
+ });
237
+ }
238
+
239
+ export function buildPieOption(props: BuilderProps): EChartsOption {
240
+ const first = props.data[0] ?? { name: '', data: [] };
241
+ const textColor = props.textColor
242
+ ?? (props.theme === 'dark' ? 'rgb(161, 161, 170)' : 'rgb(113, 113, 122)');
243
+ const innerRadius = props.type === 'donut' ? (props.innerRadius ?? '50%') : 0;
244
+ const outerRadius = props.outerRadius ?? '70%';
245
+
246
+ return {
247
+ ...baseOption(props, 'item'),
248
+ series: [{
249
+ type: 'pie',
250
+ name: first.name,
251
+ data: normalizePieData(first.data ?? [], props.categories),
252
+ radius: [innerRadius, outerRadius],
253
+ center: ['50%', '55%'],
254
+ avoidLabelOverlap: true,
255
+ label: {
256
+ show: props.showLabels ?? false,
257
+ color: textColor,
258
+ },
259
+ labelLine: {
260
+ show: props.showLabels ?? false,
261
+ ...(props.borderColor ? { lineStyle: { color: props.borderColor } } : {}),
262
+ },
263
+ emphasis: {
264
+ label: { show: props.showLabels ?? false },
265
+ },
266
+ }],
267
+ };
268
+ }
269
+
270
+ export function buildRadarOption(props: BuilderProps): EChartsOption {
271
+ const indicators = props.indicators?.length
272
+ ? props.indicators
273
+ : props.categories.map((name) => ({ name }));
274
+
275
+ return {
276
+ ...baseOption(props, 'item'),
277
+ radar: { indicator: indicators },
278
+ series: [{
279
+ type: 'radar',
280
+ data: props.data.map((series) => ({
281
+ name: series.name,
282
+ value: normalizeData(series.data),
283
+ ...(series.color ? {
284
+ itemStyle: { color: series.color },
285
+ lineStyle: { color: series.color },
286
+ } : {}),
287
+ })),
288
+ }],
289
+ };
290
+ }
291
+
292
+ export function buildGaugeOption(props: BuilderProps): EChartsOption {
293
+ const first = props.data[0] ?? { name: '', data: [] };
294
+ const [firstValue] = first.data as unknown[];
295
+ const data = typeof firstValue === 'number'
296
+ ? [{ name: first.name, value: firstValue }]
297
+ : normalizePieData(first.data ?? [], props.categories);
298
+
299
+ return {
300
+ ...baseOption(props, 'item'),
301
+ series: [{
302
+ type: 'gauge',
303
+ name: first.name,
304
+ min: props.minValue ?? 0,
305
+ max: props.maxValue ?? 100,
306
+ data,
307
+ progress: { show: true },
308
+ detail: {
309
+ valueAnimation: props.enableAnimations !== false && props.enableAnimations !== 0,
310
+ formatter: props.yAxisAppend
311
+ ? (value: number) => `${value}${props.yAxisAppend}`
312
+ : '{value}',
313
+ },
314
+ }],
315
+ };
316
+ }
317
+
318
+ export function buildFunnelOption(props: BuilderProps): EChartsOption {
319
+ const first = props.data[0] ?? { name: '', data: [] };
320
+ return {
321
+ ...baseOption(props, 'item'),
322
+ series: [{
323
+ type: 'funnel',
324
+ name: first.name,
325
+ min: props.minValue,
326
+ max: props.maxValue,
327
+ data: normalizePieData(first.data ?? [], props.categories),
328
+ label: { show: props.showLabels ?? true },
329
+ emphasis: { label: { fontWeight: 'bold' } },
330
+ }],
331
+ };
332
+ }
333
+
334
+ function isHeatmapPoint(item: unknown): item is [unknown, unknown, number] {
335
+ if (!Array.isArray(item)) return false;
336
+ const point = item as unknown[];
337
+ return typeof point[2] === 'number';
338
+ }
339
+
340
+ function heatmapExtent(data: unknown[], operation: 'max' | 'min', fallback: number): number {
341
+ const values = data
342
+ .filter(isHeatmapPoint)
343
+ .map((item) => item[2]);
344
+ return values.length ? Math[operation](...values) : fallback;
345
+ }
346
+
347
+ export function buildHeatmapOption(props: BuilderProps): EChartsOption {
348
+ const first = props.data[0] ?? { name: '', data: [] };
349
+ const data = first.data ?? [];
350
+ return {
351
+ ...baseOption(props, 'item'),
352
+ grid: { left: 60, right: 20, top: 40, bottom: 70 },
353
+ xAxis: {
354
+ type: 'category',
355
+ data: props.categories,
356
+ splitArea: { show: true },
357
+ },
358
+ yAxis: {
359
+ type: 'category',
360
+ data: props.yCategories ?? [],
361
+ splitArea: { show: true },
362
+ },
363
+ visualMap: {
364
+ min: props.minValue ?? heatmapExtent(data, 'min', 0),
365
+ max: props.maxValue ?? heatmapExtent(data, 'max', 100),
366
+ calculable: true,
367
+ orient: 'horizontal',
368
+ left: 'center',
369
+ bottom: 0,
370
+ },
371
+ series: [{
372
+ type: 'heatmap',
373
+ name: first.name,
374
+ data,
375
+ label: { show: props.showLabels ?? false },
376
+ emphasis: {
377
+ itemStyle: {
378
+ shadowBlur: 10,
379
+ shadowColor: 'rgba(0, 0, 0, 0.35)',
380
+ },
381
+ },
382
+ }],
383
+ };
384
+ }
385
+
386
+ export function buildTreemapOption(props: BuilderProps): EChartsOption {
387
+ const first = props.data[0] ?? { name: '', data: [] };
388
+ return {
389
+ ...baseOption(props, 'item'),
390
+ series: [{
391
+ type: 'treemap',
392
+ name: first.name,
393
+ data: first.data ?? [],
394
+ label: { show: props.showLabels ?? true },
395
+ upperLabel: { show: props.showLabels ?? true },
396
+ }],
397
+ };
398
+ }
399
+
400
+ export function buildSunburstOption(props: BuilderProps): EChartsOption {
401
+ const first = props.data[0] ?? { name: '', data: [] };
402
+ return {
403
+ ...baseOption(props, 'item'),
404
+ series: [{
405
+ type: 'sunburst',
406
+ name: first.name,
407
+ data: first.data ?? [],
408
+ radius: [props.innerRadius ?? 0, props.outerRadius ?? '90%'],
409
+ label: { show: props.showLabels ?? true },
410
+ emphasis: { focus: 'ancestor' },
411
+ }],
412
+ };
413
+ }
414
+
173
415
  function deriveNodes(edges: SankeyEdge[]): { name: string }[] {
174
416
  const seen = new Set<string>();
175
417
  const nodes: { name: string }[] = [];
@@ -2,9 +2,18 @@ import {
2
2
  buildAreaOption,
3
3
  buildBarOption,
4
4
  type BuilderProps,
5
+ buildFunnelOption,
6
+ buildGaugeOption,
7
+ buildHeatmapOption,
5
8
  buildLineOption,
9
+ buildPieOption,
10
+ buildRadarOption,
6
11
  buildSankeyOption,
12
+ buildScatterOption,
13
+ buildSunburstOption,
14
+ buildTreemapOption,
7
15
  type ChartType,
16
+ type RadarIndicator,
8
17
  type SeriesItem,
9
18
  } from './builders';
10
19
  import { type CSSResultGroup, html, type PropertyValues, unsafeCSS } from 'lit';
@@ -29,6 +38,8 @@ export default class ZnChart extends ZincElement {
29
38
  @property() type: ChartType = 'bar';
30
39
  @property({ type: Array }) data: SeriesItem[] = [];
31
40
  @property({ type: Array }) categories: string[] = [];
41
+ @property({ attribute: 'y-categories', type: Array }) yCategories: string[] = [];
42
+ @property({ type: Array }) indicators: RadarIndicator[] = [];
32
43
 
33
44
  @property({ attribute: 'xaxis' }) xAxis: 'datetime' | 'category' | 'numeric';
34
45
  @property({ type: Number, attribute: 'd-size' }) datapointSize: number = 1;
@@ -55,6 +66,11 @@ export default class ZnChart extends ZincElement {
55
66
  @property({ type: Array }) colors?: string[];
56
67
  @property({ attribute: 'sync-group' }) syncGroup?: string;
57
68
  @property({ type: Boolean }) smooth = false;
69
+ @property({ attribute: 'inner-radius' }) innerRadius?: number | string;
70
+ @property({ attribute: 'outer-radius' }) outerRadius?: number | string;
71
+ @property({ attribute: 'show-labels', type: Boolean }) showLabels?: boolean;
72
+ @property({ attribute: 'min-value', type: Number }) minValue?: number;
73
+ @property({ attribute: 'max-value', type: Number }) maxValue?: number;
58
74
  @property({
59
75
  converter: {
60
76
  fromAttribute: (value: string | null) => {
@@ -122,12 +138,28 @@ export default class ZnChart extends ZincElement {
122
138
  scale: this.scale,
123
139
  textColor: this.getTextColor(),
124
140
  borderColor: this.getBorderColor(),
141
+ innerRadius: this.innerRadius,
142
+ outerRadius: this.outerRadius,
143
+ showLabels: this.showLabels,
144
+ yCategories: Array.isArray(this.yCategories) ? this.yCategories : [],
145
+ indicators: Array.isArray(this.indicators) ? this.indicators : [],
146
+ minValue: this.minValue,
147
+ maxValue: this.maxValue,
125
148
  };
126
149
  switch (this.type) {
127
150
  case 'bar': return buildBarOption(props);
128
151
  case 'line': return buildLineOption(props);
129
152
  case 'area': return buildAreaOption(props);
153
+ case 'funnel': return buildFunnelOption(props);
154
+ case 'gauge': return buildGaugeOption(props);
155
+ case 'heatmap': return buildHeatmapOption(props);
156
+ case 'donut':
157
+ case 'pie': return buildPieOption(props);
158
+ case 'radar': return buildRadarOption(props);
130
159
  case 'sankey': return buildSankeyOption(props);
160
+ case 'scatter': return buildScatterOption(props);
161
+ case 'sunburst': return buildSunburstOption(props);
162
+ case 'treemap': return buildTreemapOption(props);
131
163
  default: return buildBarOption(props);
132
164
  }
133
165
  }
@@ -207,4 +239,4 @@ export default class ZnChart extends ZincElement {
207
239
  protected render(): unknown {
208
240
  return html`<div class="chart"><div id="chart"></div></div>`;
209
241
  }
210
- }
242
+ }
@@ -41,6 +41,71 @@ describe('<zn-chart>', () => {
41
41
  expect(canvas).to.exist;
42
42
  });
43
43
 
44
+ it('renders pie and donut charts', async () => {
45
+ for (const type of ['pie', 'donut']) {
46
+ const el: any = await fixture(html`
47
+ <zn-chart
48
+ type=${type}
49
+ .data=${[{ name: 'Insights', data: [8, 1] }]}
50
+ .categories=${['Configuration suggestion', 'Insecure configuration']}
51
+ ></zn-chart>
52
+ `);
53
+ await el.updateComplete;
54
+ await new Promise((r) => setTimeout(r, 50));
55
+ const canvas = el.shadowRoot.querySelector('canvas');
56
+ expect(canvas, `${type} chart canvas`).to.exist;
57
+ }
58
+ });
59
+
60
+ it('renders the additional native chart types', async () => {
61
+ const cases = [
62
+ { type: 'scatter', data: [{ name: 'Points', data: [[1, 2], [2, 4]] }] },
63
+ {
64
+ type: 'radar',
65
+ data: [{ name: 'Current', data: [80, 65, 90] }],
66
+ categories: ['Security', 'Performance', 'Reliability'],
67
+ },
68
+ { type: 'gauge', data: [{ name: 'Health', data: [82] }] },
69
+ {
70
+ type: 'funnel',
71
+ data: [{ name: 'Conversion', data: [1000, 320, 85] }],
72
+ categories: ['Visitors', 'Trials', 'Customers'],
73
+ },
74
+ {
75
+ type: 'heatmap',
76
+ data: [{ name: 'Requests', data: [[0, 0, 5], [1, 0, 12]] }],
77
+ categories: ['Mon', 'Tue'],
78
+ yCategories: ['API'],
79
+ },
80
+ {
81
+ type: 'treemap',
82
+ data: [{ name: 'Insights', data: [{ name: 'Configuration', value: 8 }] }],
83
+ },
84
+ {
85
+ type: 'sunburst',
86
+ data: [{
87
+ name: 'Insights',
88
+ data: [{ name: 'Security', children: [{ name: 'Configuration', value: 8 }] }],
89
+ }],
90
+ },
91
+ ];
92
+
93
+ for (const chartCase of cases) {
94
+ const el: any = await fixture(html`
95
+ <zn-chart
96
+ type=${chartCase.type}
97
+ .data=${chartCase.data}
98
+ .categories=${chartCase.categories ?? []}
99
+ .yCategories=${chartCase.yCategories ?? []}
100
+ ></zn-chart>
101
+ `);
102
+ await el.updateComplete;
103
+ await new Promise((r) => setTimeout(r, 50));
104
+ const canvas = el.shadowRoot.querySelector('canvas');
105
+ expect(canvas, `${chartCase.type} chart canvas`).to.exist;
106
+ }
107
+ });
108
+
44
109
  it('joins a sync-group when the attribute is set', async () => {
45
110
  const a: any = await fixture(html`
46
111
  <zn-chart sync-group="g1" type="bar"
@@ -51,4 +116,4 @@ describe('<zn-chart>', () => {
51
116
  await new Promise((r) => setTimeout(r, 50));
52
117
  expect(a.chart?.group).to.equal('g1');
53
118
  });
54
- });
119
+ });
@@ -15,13 +15,23 @@ export function loadECharts(): Promise<EChartsModule> {
15
15
  ]);
16
16
  core.use([
17
17
  charts.BarChart,
18
+ charts.FunnelChart,
19
+ charts.GaugeChart,
20
+ charts.HeatmapChart,
18
21
  charts.LineChart,
22
+ charts.PieChart,
23
+ charts.RadarChart,
19
24
  charts.SankeyChart,
25
+ charts.ScatterChart,
26
+ charts.SunburstChart,
27
+ charts.TreemapChart,
20
28
  components.GridComponent,
21
29
  components.TooltipComponent,
22
30
  components.LegendComponent,
23
31
  components.TitleComponent,
24
32
  components.DataZoomComponent,
33
+ components.RadarComponent,
34
+ components.VisualMapComponent,
25
35
  renderers.CanvasRenderer,
26
36
  ]);
27
37
  return core;
@@ -52,6 +52,10 @@ export default class ZnCollapsible extends ZincElement {
52
52
 
53
53
  @state() numberOfItems: number = 0;
54
54
 
55
+ @state() private animating: boolean = false;
56
+
57
+ private animatingTimer?: ReturnType<typeof setTimeout>;
58
+
55
59
  protected _store: Store;
56
60
 
57
61
  private readonly hasSlotController = new HasSlotController(this, '[default]', 'header', 'caption', 'label', 'description');
@@ -94,6 +98,11 @@ export default class ZnCollapsible extends ZincElement {
94
98
  }
95
99
  }
96
100
 
101
+ disconnectedCallback() {
102
+ super.disconnectedCallback();
103
+ clearTimeout(this.animatingTimer);
104
+ }
105
+
97
106
  // this is for handling global toggles
98
107
  public handleCaptionToggle = (e: ZnInputEvent) => {
99
108
  const toggle = e.target as ZnToggle;
@@ -104,8 +113,38 @@ export default class ZnCollapsible extends ZincElement {
104
113
 
105
114
  protected updated(changedProperties: PropertyValues) {
106
115
  super.updated(changedProperties);
107
- if (changedProperties.has('expanded') && this.storeKey) {
108
- this._store.set(this.storeKey, this.expanded.toString());
116
+ if (changedProperties.has('expanded')) {
117
+ if (changedProperties.get('expanded') !== undefined) {
118
+ this.startAnimating();
119
+ }
120
+
121
+ if (this.storeKey) {
122
+ this._store.set(this.storeKey, this.expanded.toString());
123
+ }
124
+ }
125
+ }
126
+
127
+ // Content is only clipped while the height transition runs, so expanded
128
+ // content can overflow the host. The timer covers a transitionend that never
129
+ // arrives (reduced motion, hidden host).
130
+ private startAnimating() {
131
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
132
+ return;
133
+ }
134
+
135
+ this.animating = true;
136
+ clearTimeout(this.animatingTimer);
137
+ this.animatingTimer = setTimeout(() => (this.animating = false), 600);
138
+ }
139
+
140
+ private stopAnimating() {
141
+ clearTimeout(this.animatingTimer);
142
+ this.animating = false;
143
+ }
144
+
145
+ private handleTransitionEnd = (e: TransitionEvent) => {
146
+ if (e.target === e.currentTarget && e.propertyName === 'grid-template-rows') {
147
+ this.stopAnimating();
109
148
  }
110
149
  }
111
150
 
@@ -160,8 +199,14 @@ export default class ZnCollapsible extends ZincElement {
160
199
  <zn-icon library="material-outlined" src="expand_more" class="expand"></zn-icon>` : ''}
161
200
  </div>
162
201
  </slot>
163
- <div class=${classMap({ 'content': true, 'content--flush': this.flush, })} part="content">
164
- <slot @slotchange="${this.requestUpdate}"></slot>
202
+ <div class=${classMap({
203
+ 'content': true,
204
+ 'content--flush': this.flush,
205
+ 'content--animating': this.animating,
206
+ })} part="content" @transitionend="${this.handleTransitionEnd}">
207
+ <div class="content__inner">
208
+ <slot @slotchange="${this.requestUpdate}"></slot>
209
+ </div>
165
210
  </div>
166
211
  </div>`;
167
212
  }
@@ -76,29 +76,48 @@
76
76
  }
77
77
  }
78
78
 
79
- :host(:not([flush])).content {
80
- @include wc.padding();
79
+ // Height animates via grid-template-rows 0fr -> 1fr; the inline padding is
80
+ // constant so the content never reflows (and re-wraps) mid-transition.
81
+ .content {
82
+ display: grid;
83
+ grid-template-rows: 0fr;
84
+ opacity: 0;
85
+ transition: grid-template-rows var(--zn-transition-medium) cubic-bezier(0.32, 0.72, 0, 1),
86
+ opacity var(--zn-transition-fast) ease-out;
87
+
88
+ &__inner {
89
+ min-height: 0;
90
+ padding: 0 var(--zn-spacing-medium) var(--zn-spacing-medium);
91
+ transform: translateY(-4px);
92
+ transition: transform var(--zn-transition-medium) cubic-bezier(0.32, 0.72, 0, 1);
93
+ }
94
+
95
+ &--flush &__inner {
96
+ padding: 0;
97
+ }
81
98
  }
82
99
 
83
- .content {
84
- transition: .1s all ease-out;
100
+ // Clipped while collapsed or mid-transition only, so expanded content can still
101
+ // overflow the host (dropdowns, tooltips).
102
+ :host(:not([expanded])) .content,
103
+ .content--animating {
104
+ overflow: hidden;
85
105
  }
86
106
 
87
107
  :host([expanded]) .content {
88
- margin: 0 var(--zn-spacing-medium);
89
- padding-bottom: var(--zn-spacing-medium);
108
+ grid-template-rows: 1fr;
109
+ opacity: 1;
90
110
 
91
- &--flush {
92
- margin: 0;
93
- padding: 0;
111
+ .content__inner {
112
+ transform: none;
94
113
  }
95
114
  }
96
115
 
97
- :host(:not([expanded])) .content {
98
- max-height: 0;
99
- opacity: 0;
100
- overflow: hidden;
101
- padding: 0;
116
+ @media (prefers-reduced-motion: reduce) {
117
+ .content,
118
+ .content__inner {
119
+ transition-duration: 0s;
120
+ }
102
121
  }
103
122
 
104
123
  .expand {
@@ -821,12 +821,15 @@ export default class ZnDataTable extends ZincElement {
821
821
  <div class="table__footer__pagination-buttons">
822
822
  <zn-button @click="${this.page !== 1 ? this.goToFirstPage : undefined}"
823
823
  ?disabled="${this.page === 1}"
824
- icon="arrow_left@lu"
825
- panel-bg>First
824
+ icon="chevrons-left@lu"
825
+ icon-button="small"
826
+ panel-bg>
826
827
  </zn-button>
827
828
  <zn-button @click="${this.page !== 1 ? this.goToPreviousPage : undefined}"
828
829
  ?disabled="${this.page === 1}"
829
- panel-bg>Back
830
+ icon="chevron-left@lu"
831
+ icon-button="small"
832
+ panel-bg>
830
833
  </zn-button>
831
834
  ${this.getPageRange().map((p) => p === 'ellipsis'
832
835
  ? html`<span class="table__footer__pagination-ellipsis">…</span>`
@@ -838,13 +841,15 @@ export default class ZnDataTable extends ZincElement {
838
841
  )}
839
842
  <zn-button @click="${this.page !== this.totalPages ? this.goToNextPage : undefined}"
840
843
  ?disabled="${this.page === this.totalPages}"
841
- panel-bg>Next
844
+ icon="chevron-right@lu"
845
+ icon-button="small"
846
+ panel-bg>
842
847
  </zn-button>
843
848
  <zn-button @click="${this.page !== this.totalPages ? this.goToLastPage : undefined}"
844
849
  ?disabled="${this.page === this.totalPages}"
845
- icon="arrow_right@lu"
846
- icon-position="right"
847
- panel-bg>Last
850
+ icon="chevrons-right@lu"
851
+ icon-button="small"
852
+ panel-bg>
848
853
  </zn-button>
849
854
  </div>`;
850
855
  }
@@ -1039,7 +1044,7 @@ export default class ZnDataTable extends ZincElement {
1039
1044
  if (header?.cellTemplate !== undefined) {
1040
1045
  data = {...header.cellTemplate, ...data}
1041
1046
  }
1042
-
1047
+
1043
1048
  let content: TemplateResult | ZincElement = html`${data.text}`;
1044
1049
 
1045
1050
  if (data.style || data.color) {
@@ -258,7 +258,7 @@ table {
258
258
  display: flex;
259
259
  flex-direction: row;
260
260
  justify-content: space-between;
261
- padding: var(--zn-spacing-medium) 0;
261
+ padding: var(--zn-spacing-small) 0;
262
262
 
263
263
  &__right, &__left {
264
264
  display: flex;
@@ -24,7 +24,7 @@
24
24
  white-space: nowrap;
25
25
  text-overflow: ellipsis;
26
26
  display: flex;
27
- gap: var(--zn-spacing-x-small);
27
+ gap: var(--zn-spacing-2x-small);
28
28
  align-items: center;
29
29
  text-wrap: auto;
30
30
  }