@aiaiai-pt/design-system 0.8.0 → 0.8.2
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.
|
@@ -28,6 +28,13 @@
|
|
|
28
28
|
@example Loading
|
|
29
29
|
<DataTable {columns} rows={[]} loading />
|
|
30
30
|
|
|
31
|
+
@example Responsive (default)
|
|
32
|
+
Tables card-stack below `card_breakpoint` (default 768px) so columns
|
|
33
|
+
don't scroll off-screen on phones/tablets. The first column becomes the
|
|
34
|
+
card title; the rest render as label:value rows. Opt out with
|
|
35
|
+
`responsive={false}`, or raise the breakpoint for wide tables.
|
|
36
|
+
<DataTable {columns} {rows} card_breakpoint={1024} />
|
|
37
|
+
|
|
31
38
|
@example Custom cell rendering (badges, chips, components)
|
|
32
39
|
Pass a `cell` snippet to override the default per-cell text rendering.
|
|
33
40
|
The default behavior (using `column.render` to produce a string) is
|
|
@@ -70,6 +77,21 @@
|
|
|
70
77
|
selected_rows = $bindable(new Set()),
|
|
71
78
|
/** @type {string} */
|
|
72
79
|
row_key = 'id',
|
|
80
|
+
/**
|
|
81
|
+
* Responsive card mode. When true (the default), the table restyles
|
|
82
|
+
* into a stack of cards below `card_breakpoint` so its columns don't
|
|
83
|
+
* scroll off-screen on narrow viewports. Set false to keep the table
|
|
84
|
+
* layout at every width (e.g. tables that are already narrow).
|
|
85
|
+
* @type {boolean}
|
|
86
|
+
*/
|
|
87
|
+
responsive = true,
|
|
88
|
+
/**
|
|
89
|
+
* Viewport width (px) at/below which `responsive` switches to cards.
|
|
90
|
+
* A prop rather than a fixed media query so consumers with wider
|
|
91
|
+
* tables (many columns) can raise it.
|
|
92
|
+
* @type {number}
|
|
93
|
+
*/
|
|
94
|
+
card_breakpoint = 768,
|
|
73
95
|
/** @type {string} */
|
|
74
96
|
empty_heading = 'No data',
|
|
75
97
|
/** @type {string} */
|
|
@@ -106,6 +128,22 @@
|
|
|
106
128
|
|
|
107
129
|
const SKELETON_ROWS = 5;
|
|
108
130
|
|
|
131
|
+
// Responsive card mode is toggled by a CLASS driven by matchMedia, not
|
|
132
|
+
// a static @media query — that keeps `card_breakpoint` a runtime prop
|
|
133
|
+
// while still rendering a SINGLE table DOM (the cells are restyled, not
|
|
134
|
+
// duplicated). SSR renders the table (is_card=false); the effect flips
|
|
135
|
+
// it after mount, so the initial client render matches SSR (no
|
|
136
|
+
// hydration mismatch) and narrow viewports get a CSS-only restyle.
|
|
137
|
+
let is_card = $state(false);
|
|
138
|
+
$effect(() => {
|
|
139
|
+
if (!responsive || typeof window === 'undefined' || !window.matchMedia) return;
|
|
140
|
+
const mq = window.matchMedia(`(max-width: ${card_breakpoint}px)`);
|
|
141
|
+
const sync = () => (is_card = mq.matches);
|
|
142
|
+
sync();
|
|
143
|
+
mq.addEventListener('change', sync);
|
|
144
|
+
return () => mq.removeEventListener('change', sync);
|
|
145
|
+
});
|
|
146
|
+
|
|
109
147
|
const all_selected = $derived(
|
|
110
148
|
rows.length > 0 &&
|
|
111
149
|
rows.every((row) => selected_rows.has(String(row[row_key])))
|
|
@@ -176,7 +214,7 @@
|
|
|
176
214
|
}
|
|
177
215
|
</script>
|
|
178
216
|
|
|
179
|
-
<div class="table-wrap {className}" {...rest}>
|
|
217
|
+
<div class="table-wrap {className}" class:is-card={responsive && is_card} {...rest}>
|
|
180
218
|
{#if children}
|
|
181
219
|
<div class="table-toolbar">
|
|
182
220
|
{@render children()}
|
|
@@ -288,8 +326,12 @@
|
|
|
288
326
|
/>
|
|
289
327
|
</td>
|
|
290
328
|
{/if}
|
|
291
|
-
{#each columns as col}
|
|
292
|
-
<td
|
|
329
|
+
{#each columns as col, col_index}
|
|
330
|
+
<td
|
|
331
|
+
class="table-td"
|
|
332
|
+
class:table-td-card-title={col_index === 0}
|
|
333
|
+
data-label={col.label}
|
|
334
|
+
>
|
|
293
335
|
{#if cell}
|
|
294
336
|
{@render cell({ row, column: col, value: row[col.key] })}
|
|
295
337
|
{:else}
|
|
@@ -453,4 +495,83 @@
|
|
|
453
495
|
cursor: pointer;
|
|
454
496
|
display: block;
|
|
455
497
|
}
|
|
498
|
+
|
|
499
|
+
/* ─── Responsive card mode ───
|
|
500
|
+
Toggled by `.is-card` (set via matchMedia against `card_breakpoint`,
|
|
501
|
+
not a static @media query, so the breakpoint stays a prop). The SAME
|
|
502
|
+
table is restyled into a stack of cards — one DOM, no duplicate text
|
|
503
|
+
nodes — so getByText / the a11y tree see a single set of cells. Each
|
|
504
|
+
row becomes a card; each cell a `label : value` line whose label is
|
|
505
|
+
the column header surfaced via `data-label`; the first column renders
|
|
506
|
+
as the card title. */
|
|
507
|
+
.table-wrap.is-card {
|
|
508
|
+
border: none;
|
|
509
|
+
border-radius: 0;
|
|
510
|
+
overflow: visible;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.table-wrap.is-card .table-scroll {
|
|
514
|
+
overflow: visible;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.table-wrap.is-card .table,
|
|
518
|
+
.table-wrap.is-card .table-body {
|
|
519
|
+
display: block;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.table-wrap.is-card .table-head {
|
|
523
|
+
display: none;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.table-wrap.is-card .table-row {
|
|
527
|
+
display: block;
|
|
528
|
+
padding: var(--space-md);
|
|
529
|
+
border: var(--elevation-border);
|
|
530
|
+
border-radius: var(--radius-md);
|
|
531
|
+
background: var(--color-surface);
|
|
532
|
+
margin-bottom: var(--space-sm);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/* Cards are uniform — drop the table's zebra striping. */
|
|
536
|
+
.table-wrap.is-card .table-row-even {
|
|
537
|
+
background: var(--color-surface);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.table-wrap.is-card .table-td {
|
|
541
|
+
display: flex;
|
|
542
|
+
align-items: center;
|
|
543
|
+
justify-content: space-between;
|
|
544
|
+
gap: var(--space-md);
|
|
545
|
+
padding: var(--space-2xs) 0;
|
|
546
|
+
border-bottom: none;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.table-wrap.is-card .table-td::before {
|
|
550
|
+
content: attr(data-label);
|
|
551
|
+
flex-shrink: 0;
|
|
552
|
+
font-family: var(--type-label-font);
|
|
553
|
+
font-size: var(--type-label-size);
|
|
554
|
+
letter-spacing: var(--type-label-tracking);
|
|
555
|
+
color: var(--color-text-secondary);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* First column → full-width card heading, no label prefix. */
|
|
559
|
+
.table-wrap.is-card .table-td-card-title {
|
|
560
|
+
display: block;
|
|
561
|
+
padding: 0 0 var(--space-2xs);
|
|
562
|
+
font-weight: var(--raw-font-weight-medium, 500);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.table-wrap.is-card .table-td-card-title::before {
|
|
566
|
+
content: none;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/* Selection checkbox keeps its own line; no empty label pseudo. */
|
|
570
|
+
.table-wrap.is-card .table-td-check {
|
|
571
|
+
justify-content: flex-start;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.table-wrap.is-card .table-td-check::before {
|
|
575
|
+
content: none;
|
|
576
|
+
}
|
|
456
577
|
</style>
|
package/components/Toggle.svelte
CHANGED
|
@@ -43,12 +43,19 @@
|
|
|
43
43
|
</script>
|
|
44
44
|
|
|
45
45
|
<div class="toggle-group {className}">
|
|
46
|
+
<!--
|
|
47
|
+
type="button" is critical: a default <button> inside a <form> is
|
|
48
|
+
type="submit", so every Toggle click inside a form was silently
|
|
49
|
+
submitting it. Declared BEFORE {...rest} so the consumer can still
|
|
50
|
+
override (e.g. type="submit" if they really want submit semantics).
|
|
51
|
+
-->
|
|
46
52
|
<button
|
|
47
53
|
id={toggleId}
|
|
48
54
|
class="toggle"
|
|
49
55
|
class:toggle-on={checked}
|
|
50
56
|
class:toggle-disabled={disabled}
|
|
51
57
|
{disabled}
|
|
58
|
+
type="button"
|
|
52
59
|
role="switch"
|
|
53
60
|
aria-checked={checked}
|
|
54
61
|
aria-labelledby={label ? `${toggleId}-label` : undefined}
|