@attrus-ds/core 1.0.13 → 1.0.14
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/dist/css/components/buttons.css +46 -15
- package/dist/css/components/callout.css +45 -8
- package/dist/css/components/combobox.css +36 -23
- package/dist/css/components/inputs.css +55 -22
- package/dist/css/components/overlays.css +1 -1
- package/dist/css/components/primitives.css +68 -0
- package/dist/css/components/sidebar.css +33 -0
- package/dist/css/components/smart-search.css +56 -23
- package/dist/css/components/status-pills.css +6 -6
- package/dist/css/components/table.css +602 -36
- package/dist/css/components/tabs.css +1 -0
- package/dist/css/components/toggle-chip.css +2 -2
- package/dist/css/components/topbar.css +180 -20
- package/dist/css/tokens.css +50 -10
- package/dist/react/Button/Button.d.ts +10 -2
- package/dist/react/Button/Button.tsx +12 -4
- package/dist/react/Combobox/Combobox.d.ts +5 -0
- package/dist/react/Combobox/Combobox.tsx +26 -4
- package/dist/react/DataTable/DataTable.d.ts +91 -0
- package/dist/react/DataTable/DataTable.tsx +413 -35
- package/dist/react/SearchPill/SearchPill.d.ts +3 -0
- package/dist/react/SearchPill/SearchPill.tsx +6 -1
- package/dist/react/SmartSearch/SmartSearch.d.ts +4 -0
- package/dist/react/SmartSearch/SmartSearch.tsx +14 -1
- package/package.json +1 -1
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
[.dt-pagination] bottom — page nav + range
|
|
13
13
|
|
|
14
14
|
Sizes:
|
|
15
|
-
.dt
|
|
15
|
+
.dt-sm compact — 8 / 12 padding, fs-xs body, fs-xxs head
|
|
16
16
|
default standard — 12 / 16 padding, fs-md body, fs-xxs head
|
|
17
|
-
.dt
|
|
17
|
+
.dt-lg spacious — 16 / 20 padding, fs-md body, fs-xs head
|
|
18
18
|
|
|
19
19
|
Density helpers:
|
|
20
|
-
.dt
|
|
21
|
-
.dt
|
|
22
|
-
.dt
|
|
20
|
+
.dt-zebra alternating row backgrounds
|
|
21
|
+
.dt-sticky thead sticks to top while scrolling
|
|
22
|
+
.dt-bordered vertical column borders
|
|
23
23
|
|
|
24
24
|
Cell helpers:
|
|
25
25
|
.dt-cell-stack primary line + sub line
|
|
@@ -63,8 +63,11 @@
|
|
|
63
63
|
display: flex;
|
|
64
64
|
align-items: center;
|
|
65
65
|
gap: var(--space-4);
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
/* Box on the element, not on a descendant selector: the height must survive a
|
|
67
|
+
toolbar that is not a direct child of .dt-wrap, or the selecting state
|
|
68
|
+
renders 5px taller and the table jumps when the first row is ticked. */
|
|
69
|
+
height: 52px;
|
|
70
|
+
padding: 0 var(--space-5);
|
|
68
71
|
box-sizing: border-box;
|
|
69
72
|
border-bottom: 1px solid var(--color-border-subtle);
|
|
70
73
|
background: var(--color-background-surface);
|
|
@@ -120,8 +123,8 @@
|
|
|
120
123
|
display: inline-flex;
|
|
121
124
|
align-items: center;
|
|
122
125
|
justify-content: center;
|
|
123
|
-
height:
|
|
124
|
-
min-width:
|
|
126
|
+
height: var(--control-h-xs);
|
|
127
|
+
min-width: var(--control-h-xs);
|
|
125
128
|
padding: 0 var(--space-3);
|
|
126
129
|
border: 1px solid var(--color-border-default);
|
|
127
130
|
background: var(--color-background-surface);
|
|
@@ -137,6 +140,19 @@
|
|
|
137
140
|
border-color var(--transition-fast),
|
|
138
141
|
color var(--transition-fast);
|
|
139
142
|
}
|
|
143
|
+
/* The ellipsis is not a control: no box, no hover, no tab stop — a boxed gap
|
|
144
|
+
would read as a page you can go to. */
|
|
145
|
+
.dt-pagination .page-gap {
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
min-width: var(--space-5);
|
|
150
|
+
font-family: var(--font-mono);
|
|
151
|
+
font-size-adjust: var(--font-mono-adjust);
|
|
152
|
+
font-size: var(--fs-xs);
|
|
153
|
+
color: var(--color-foreground-muted);
|
|
154
|
+
user-select: none;
|
|
155
|
+
}
|
|
140
156
|
.dt-pagination .page-btn:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
|
|
141
157
|
.dt-pagination .page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
142
158
|
.dt-pagination .page-btn.is-current {
|
|
@@ -185,23 +201,65 @@
|
|
|
185
201
|
|
|
186
202
|
/* Sortable header */
|
|
187
203
|
.dt-th-sort {
|
|
204
|
+
/* A sortable header must be keyboard-operable, so it is a <button> — which
|
|
205
|
+
arrives with the UA's Arial, grey fill and 2px border. Reset those and
|
|
206
|
+
inherit the <th>'s own typography, so the control looks like the header it
|
|
207
|
+
replaces rather than a button parked inside one. */
|
|
208
|
+
appearance: none;
|
|
209
|
+
background: none;
|
|
210
|
+
border: 0;
|
|
211
|
+
padding: 0;
|
|
212
|
+
margin: 0;
|
|
213
|
+
font: inherit;
|
|
214
|
+
letter-spacing: inherit;
|
|
215
|
+
text-transform: inherit;
|
|
216
|
+
color: inherit;
|
|
217
|
+
text-align: inherit;
|
|
218
|
+
display: inline-flex;
|
|
219
|
+
align-items: center;
|
|
188
220
|
cursor: pointer;
|
|
189
221
|
user-select: none;
|
|
190
222
|
transition: color var(--transition-fast);
|
|
191
223
|
}
|
|
224
|
+
.dt-th-sort:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--radius-xs); }
|
|
192
225
|
.dt-th-sort:hover { color: var(--color-foreground-default); }
|
|
226
|
+
.dt-th-sort { gap: var(--space-2); }
|
|
227
|
+
/* The chevron is a CLIPPED SLOT holding both arrows stacked, and the track
|
|
228
|
+
slides between them. Rotating a single chevron 180° also reads as a direction
|
|
229
|
+
change, but it spins through horizontal — a moment where the glyph points at
|
|
230
|
+
neither value. Sliding keeps an arrow pointing the whole way: the outgoing one
|
|
231
|
+
leaves in the direction it means, the incoming one arrives from the opposite
|
|
232
|
+
edge, so the motion itself carries "ascending" or "descending".
|
|
233
|
+
Slot height must equal each arrow's height, or the track lands off-centre. */
|
|
193
234
|
.dt-th-sort .chev {
|
|
194
|
-
display:
|
|
195
|
-
width: 10px;
|
|
196
|
-
height: 10px;
|
|
235
|
+
display: block;
|
|
236
|
+
width: var(--sort-chev, 10px);
|
|
237
|
+
height: var(--sort-chev, 10px);
|
|
197
238
|
margin-left: var(--space-2);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
transition: color var(--transition-fast)
|
|
239
|
+
flex-shrink: 0;
|
|
240
|
+
overflow: hidden;
|
|
241
|
+
color: var(--color-foreground-muted);
|
|
242
|
+
transition: color var(--transition-fast);
|
|
243
|
+
}
|
|
244
|
+
.dt-th-sort .chev-track {
|
|
245
|
+
display: block;
|
|
246
|
+
/* Unsorted rests on the down arrow — the conventional "click to sort" hint. */
|
|
247
|
+
transform: translateY(calc(-1 * var(--sort-chev, 10px)));
|
|
248
|
+
transition: transform var(--duration-base) var(--ease-standard);
|
|
202
249
|
}
|
|
203
|
-
.dt-th-sort.
|
|
250
|
+
.dt-th-sort .chev-track > svg,
|
|
251
|
+
.dt-th-sort .chev-track > i {
|
|
252
|
+
display: block;
|
|
253
|
+
width: var(--sort-chev, 10px);
|
|
254
|
+
height: var(--sort-chev, 10px);
|
|
255
|
+
}
|
|
256
|
+
.dt-th-sort.is-asc .chev-track { transform: translateY(0); }
|
|
257
|
+
.dt-th-sort.is-desc .chev-track { transform: translateY(calc(-1 * var(--sort-chev, 10px))); }
|
|
258
|
+
.dt-th-sort.is-asc .chev,
|
|
204
259
|
.dt-th-sort.is-desc .chev { color: var(--color-foreground-accent); }
|
|
260
|
+
@media (prefers-reduced-motion: reduce) {
|
|
261
|
+
.dt-th-sort .chev-track { transition: none; }
|
|
262
|
+
}
|
|
205
263
|
|
|
206
264
|
/* ----------------------------------------------------------------
|
|
207
265
|
4. BODY ROWS
|
|
@@ -305,8 +363,30 @@
|
|
|
305
363
|
.dt-cell-lead > .ic-round > i, .dt-cell-lead > .ic-round > svg,
|
|
306
364
|
.dt-cell-lead > .ic-round > svg { width: var(--icon-sm); height: var(--icon-sm); stroke-width: var(--icon-stroke-bold); }
|
|
307
365
|
|
|
366
|
+
/* Trailing round icon — same badge, after the label.
|
|
367
|
+
Leading answers "what kind of row is this" and is scanned down the column;
|
|
368
|
+
trailing annotates the label itself ("verified", "has a note"), so it must
|
|
369
|
+
not enter the vertical scan. Hence margin-left:auto is NOT used: the badge
|
|
370
|
+
hugs the text instead of floating to the cell edge, where it would read as
|
|
371
|
+
a separate column. */
|
|
372
|
+
.dt-cell-lead > .ic-round.is-trailing { order: 2; margin-left: var(--space-1); }
|
|
373
|
+
.dt-cell-lead > .ic-round.is-sm { width: 20px; height: 20px; }
|
|
374
|
+
.dt-cell-lead > .ic-round.is-sm > i,
|
|
375
|
+
.dt-cell-lead > .ic-round.is-sm > svg { width: 11px; height: 11px; }
|
|
376
|
+
/* The label must keep the shrink so a long name truncates before the badge */
|
|
377
|
+
.dt-cell-lead > .ic-round.is-trailing ~ * { min-width: 0; }
|
|
378
|
+
|
|
308
379
|
/* Numbers — explicit class wins over per-cell .right alignment */
|
|
309
380
|
.dt-num {
|
|
381
|
+
/* Amounts are what the eye hunts for in a ledger, so they carry the emphasis:
|
|
382
|
+
mono ExtraBold (--w-mono-emphasis). A monospaced face cannot widen — it must
|
|
383
|
+
keep its cell — so weight there buys ink, not width: which is why 800 reads
|
|
384
|
+
as emphasis without shouting.
|
|
385
|
+
The currency chip and any sub line stay regular — see below — so the pair
|
|
386
|
+
gives one strong figure against quiet context instead of a bold block. */
|
|
387
|
+
font-family: var(--font-mono);
|
|
388
|
+
font-size-adjust: var(--font-mono-adjust);
|
|
389
|
+
font-weight: var(--w-mono-emphasis);
|
|
310
390
|
font-variant-numeric: tabular-nums;
|
|
311
391
|
font-feature-settings: 'tnum' on, 'lnum' on;
|
|
312
392
|
text-align: right;
|
|
@@ -317,13 +397,16 @@
|
|
|
317
397
|
by specificity); stacked cells in a numeric column align flex to the right. */
|
|
318
398
|
.dt thead th.dt-num { text-align: right; }
|
|
319
399
|
.dt td.dt-num .dt-cell-stack { align-items: flex-end; }
|
|
400
|
+
/* Context under an amount stays regular, or the whole cell reads as emphasis */
|
|
401
|
+
.dt td.dt-num .dt-cell-stack .sub { font-weight: var(--w-regular); }
|
|
320
402
|
.dt-num .ccy {
|
|
321
403
|
color: var(--color-foreground-muted);
|
|
322
404
|
font-weight: var(--w-regular);
|
|
323
405
|
margin-right: var(--space-2);
|
|
324
406
|
font-size: var(--fs-sm);
|
|
325
407
|
}
|
|
326
|
-
.
|
|
408
|
+
/* .bold now only steps the SIZE up — weight is already bold by default */
|
|
409
|
+
.dt-num.bold { font-size: var(--fs-base); }
|
|
327
410
|
.dt-num.delta-pos { color: var(--color-feedback-success-foreground); }
|
|
328
411
|
.dt-num.delta-neg { color: var(--color-feedback-danger-foreground); }
|
|
329
412
|
|
|
@@ -367,30 +450,30 @@
|
|
|
367
450
|
/* ----------------------------------------------------------------
|
|
368
451
|
6. SIZES
|
|
369
452
|
---------------------------------------------------------------- */
|
|
370
|
-
.dt
|
|
371
|
-
.dt
|
|
372
|
-
.dt
|
|
373
|
-
.dt
|
|
374
|
-
.dt
|
|
375
|
-
.dt
|
|
376
|
-
.dt
|
|
453
|
+
.dt-sm thead th { padding: var(--space-2) var(--space-4); }
|
|
454
|
+
.dt-sm tbody td { padding: var(--space-3) var(--space-4); font-size: var(--fs-sm); }
|
|
455
|
+
.dt-sm .dt-cell-stack > .pri { font-size: var(--fs-sm); }
|
|
456
|
+
.dt-sm .dt-cell-stack > .sub { font-size: var(--fs-xxs); }
|
|
457
|
+
.dt-sm .dt-cell-lead > .ic-tile { width: 28px; height: var(--control-h-xs); border-radius: var(--radius-sm); }
|
|
458
|
+
.dt-sm .dt-cell-lead > .ic-tile > i, .dt-sm .dt-cell-lead > .ic-tile > svg,
|
|
459
|
+
.dt-sm .dt-cell-lead > .ic-tile > svg { width: var(--icon-sm); height: var(--icon-sm); }
|
|
377
460
|
|
|
378
|
-
.dt
|
|
379
|
-
.dt
|
|
380
|
-
.dt
|
|
381
|
-
.dt
|
|
461
|
+
.dt-lg thead th { padding: var(--space-4) var(--space-6); font-size: var(--fs-xs); }
|
|
462
|
+
.dt-lg tbody td { padding: var(--space-5) var(--space-6); font-size: var(--fs-base); }
|
|
463
|
+
.dt-lg .dt-cell-stack > .pri { font-size: var(--fs-base); }
|
|
464
|
+
.dt-lg .dt-cell-lead > .ic-tile { width: 40px; height: 40px; }
|
|
382
465
|
|
|
383
466
|
|
|
384
467
|
/* ----------------------------------------------------------------
|
|
385
468
|
7. DENSITY HELPERS
|
|
386
469
|
---------------------------------------------------------------- */
|
|
387
470
|
/* Zebra — alternating bg */
|
|
388
|
-
.dt
|
|
471
|
+
.dt-zebra tbody tr:nth-child(even):not(.is-selected):not(:hover) {
|
|
389
472
|
background: color-mix(in srgb, var(--color-background-subtle) 55%, var(--color-background-surface));
|
|
390
473
|
}
|
|
391
474
|
|
|
392
475
|
/* Sticky head */
|
|
393
|
-
.dt
|
|
476
|
+
.dt-sticky thead th {
|
|
394
477
|
position: sticky;
|
|
395
478
|
top: 0;
|
|
396
479
|
z-index: 1;
|
|
@@ -399,8 +482,8 @@
|
|
|
399
482
|
}
|
|
400
483
|
|
|
401
484
|
/* Vertical column borders */
|
|
402
|
-
.dt
|
|
403
|
-
.dt
|
|
485
|
+
.dt-bordered th + th,
|
|
486
|
+
.dt-bordered td + td {
|
|
404
487
|
border-left: 1px solid var(--color-border-subtle);
|
|
405
488
|
}
|
|
406
489
|
|
|
@@ -547,15 +630,24 @@
|
|
|
547
630
|
.dt-responsive .dt td { display: block; width: 100%; box-sizing: border-box; }
|
|
548
631
|
|
|
549
632
|
/* Each row becomes a card */
|
|
633
|
+
.dt-responsive .dt tbody { padding-top: var(--space-3); }
|
|
550
634
|
.dt-responsive .dt tbody tr {
|
|
551
635
|
border: 1px solid var(--color-border-default);
|
|
552
636
|
border-radius: var(--radius-md);
|
|
553
637
|
background: var(--color-background-surface);
|
|
554
638
|
padding: var(--space-2) var(--space-4);
|
|
555
|
-
|
|
639
|
+
/* Inset horizontally, not just vertically. A card flush against the wrapper
|
|
640
|
+
edge reads as a table row that lost its table, and its own border lands
|
|
641
|
+
exactly on the wrapper's — two hairlines touching. The gutter is what makes
|
|
642
|
+
the stack read as cards. */
|
|
643
|
+
margin: 0 var(--space-3) var(--space-3);
|
|
644
|
+
/* Not width:100% — the block rule above sets it, and with side margins the
|
|
645
|
+
card would measure 100% PLUS the gutters and hang off the right edge.
|
|
646
|
+
auto lets it shrink to the space the margins leave. */
|
|
647
|
+
width: auto;
|
|
556
648
|
box-shadow: var(--shadow-xs);
|
|
557
649
|
}
|
|
558
|
-
.dt-responsive .dt tbody tr:last-child { margin-bottom:
|
|
650
|
+
.dt-responsive .dt tbody tr:last-child { margin-bottom: var(--space-3); border-bottom: 1px solid var(--color-border-default); }
|
|
559
651
|
|
|
560
652
|
/* Each cell becomes a label : value line */
|
|
561
653
|
.dt-responsive .dt tbody td {
|
|
@@ -752,7 +844,9 @@
|
|
|
752
844
|
visibly hanging off its parent card. */
|
|
753
845
|
@media (max-width: 900px) {
|
|
754
846
|
.dt-responsive .dt tbody tr.dt-subrow {
|
|
755
|
-
|
|
847
|
+
/* Pulls up under its parent card (which now carries a bottom gutter) and
|
|
848
|
+
keeps the same side inset, so parent and child read as one stack. */
|
|
849
|
+
margin: calc(-1 * var(--space-3)) var(--space-5) var(--space-3);
|
|
756
850
|
border-radius: var(--shape-control);
|
|
757
851
|
border: 1px solid var(--color-border-default);
|
|
758
852
|
border-top: 0;
|
|
@@ -761,3 +855,475 @@
|
|
|
761
855
|
.dt-responsive .dt tbody tr.dt-subrow > td { border-bottom: 0; padding-left: var(--space-4); }
|
|
762
856
|
.dt-responsive .dt tbody tr.dt-subrow > td::before { display: none; }
|
|
763
857
|
}
|
|
858
|
+
|
|
859
|
+
/* ----------------------------------------------------------------
|
|
860
|
+
12b. COLLAPSIBLE SUBROWS — disclosure + animated reveal
|
|
861
|
+
----------------------------------------------------------------
|
|
862
|
+
Same subrow vocabulary as §12, but the group starts closed and the parent
|
|
863
|
+
carries a disclosure control.
|
|
864
|
+
|
|
865
|
+
Structure:
|
|
866
|
+
<tr class="has-subrows is-collapsible is-open">
|
|
867
|
+
<td>… <button class="dt-subrow-toggle" aria-expanded="true"
|
|
868
|
+
aria-controls="grp-1">chevron</button> …
|
|
869
|
+
<tr class="dt-subrow is-collapsible" id="grp-1">
|
|
870
|
+
<td><div class="dt-subrow-inner"><div class="dt-subrow-cell">…</div></div></td>
|
|
871
|
+
|
|
872
|
+
WHY THE DOUBLE WRAPPER: a <tr> cannot be animated. height/max-height on a row
|
|
873
|
+
is ignored by table layout, and display:grid on a <td> destroys column
|
|
874
|
+
alignment. So the animation lives on a wrapper INSIDE the cell:
|
|
875
|
+
.dt-subrow-inner is a grid animating grid-template-rows 0fr -> 1fr, and its
|
|
876
|
+
single child clips the overflow. That is the one height transition that needs
|
|
877
|
+
no magic pixel value, so a subrow can hold two lines on mobile and still
|
|
878
|
+
animate correctly.
|
|
879
|
+
|
|
880
|
+
Cell padding also lives on .dt-subrow-cell rather than the <td>: padding on
|
|
881
|
+
the cell would keep the row 24px tall when closed, leaving a visible gap.
|
|
882
|
+
|
|
883
|
+
Support: grid-template-rows interpolation needs Chrome 117+, Safari 17.4+,
|
|
884
|
+
Firefox 127+. Older engines skip the tween and snap open/closed — the
|
|
885
|
+
disclosure still works, it just does not animate.
|
|
886
|
+
---------------------------------------------------------------- */
|
|
887
|
+
|
|
888
|
+
/* Closed by default; .is-open on the SUBROW drives the reveal */
|
|
889
|
+
.dt tbody tr.dt-subrow.is-collapsible > td {
|
|
890
|
+
padding-top: 0;
|
|
891
|
+
padding-bottom: 0;
|
|
892
|
+
}
|
|
893
|
+
/* The reveal animates max-height on a clipped wrapper.
|
|
894
|
+
The modern `grid-template-rows: 0fr -> 1fr` pattern was tried first and does
|
|
895
|
+
NOT work inside a table cell: a <td> has no definite height, so 1fr has
|
|
896
|
+
nothing to resolve against and computes to 0px — the group stays shut. That is
|
|
897
|
+
a property of table layout, not a bug in the pattern.
|
|
898
|
+
The cost of max-height is a cap: --subrow-max is the tallest a subrow may grow
|
|
899
|
+
before it starts clipping. It is generous enough for two wrapped lines, and it
|
|
900
|
+
is a token so a product with taller subrows can raise it. */
|
|
901
|
+
.dt-subrow-inner {
|
|
902
|
+
overflow: hidden;
|
|
903
|
+
max-height: 0;
|
|
904
|
+
transition: max-height var(--duration-slow) var(--ease-standard);
|
|
905
|
+
}
|
|
906
|
+
.dt tbody tr.dt-subrow.is-open .dt-subrow-inner { max-height: 120px; } /* literal on purpose: a token here breaks until the cached colors_and_type.css refreshes */
|
|
907
|
+
|
|
908
|
+
/* The padding the <td> gave up */
|
|
909
|
+
.dt-subrow-cell {
|
|
910
|
+
/* Padding starts at zero and arrives with the reveal. A grid item's padding
|
|
911
|
+
counts toward its automatic minimum size, so 8px of vertical padding floors
|
|
912
|
+
the 0fr track at 16px and leaves a gap under a closed group — min-height:0
|
|
913
|
+
does not remove it. Transitioning the padding alongside the track is what
|
|
914
|
+
lets the row reach a true zero. */
|
|
915
|
+
padding: 0;
|
|
916
|
+
display: flex;
|
|
917
|
+
align-items: center;
|
|
918
|
+
gap: var(--space-3);
|
|
919
|
+
min-width: 0;
|
|
920
|
+
/* Fade trails the height so the text does not appear before it has room */
|
|
921
|
+
opacity: 0;
|
|
922
|
+
/* Only opacity transitions. Animating padding too made the JS height read
|
|
923
|
+
race it: scrollHeight was sampled while padding was still ~0, so the target
|
|
924
|
+
undershot by the padding and clipped the last line. A padding snap is
|
|
925
|
+
invisible inside a clipped box. */
|
|
926
|
+
transition: opacity var(--duration-fast) var(--ease-standard);
|
|
927
|
+
}
|
|
928
|
+
.dt tbody tr.dt-subrow.is-open .dt-subrow-cell {
|
|
929
|
+
padding: var(--space-3) 0;
|
|
930
|
+
opacity: 1;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/* A closed group must not glue to the parent — the parent keeps its own border
|
|
934
|
+
until the group opens, otherwise two collapsed rows read as one. */
|
|
935
|
+
.dt tbody tr.has-subrows.is-collapsible:not(.is-open) > td {
|
|
936
|
+
border-bottom: 1px solid var(--color-border-default);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/* Disclosure control — sits in the parent's lead cell */
|
|
940
|
+
.dt-subrow-toggle {
|
|
941
|
+
flex-shrink: 0;
|
|
942
|
+
width: 20px; height: 20px;
|
|
943
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
944
|
+
padding: 0;
|
|
945
|
+
border: 0;
|
|
946
|
+
border-radius: var(--radius-2xs);
|
|
947
|
+
background: transparent;
|
|
948
|
+
color: var(--color-foreground-muted);
|
|
949
|
+
cursor: pointer;
|
|
950
|
+
transition: background var(--transition-fast), color var(--transition-fast);
|
|
951
|
+
}
|
|
952
|
+
.dt-subrow-toggle:hover { background: var(--color-background-hover); color: var(--color-foreground-default); }
|
|
953
|
+
.dt-subrow-toggle:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
954
|
+
.dt-subrow-toggle > i,
|
|
955
|
+
.dt-subrow-toggle > svg {
|
|
956
|
+
width: 14px; height: 14px;
|
|
957
|
+
stroke-width: var(--icon-stroke);
|
|
958
|
+
transition: transform var(--duration-fast) var(--ease-standard);
|
|
959
|
+
}
|
|
960
|
+
.dt-subrow-toggle[aria-expanded="true"] > i,
|
|
961
|
+
.dt-subrow-toggle[aria-expanded="true"] > svg { transform: rotate(90deg); }
|
|
962
|
+
|
|
963
|
+
/* Count badge beside the toggle — tells the user what is hidden before opening */
|
|
964
|
+
.dt-subrow-count {
|
|
965
|
+
font-family: var(--font-mono); font-size-adjust: var(--font-mono-adjust);
|
|
966
|
+
font-size: var(--fs-xxs);
|
|
967
|
+
color: var(--color-foreground-muted);
|
|
968
|
+
background: var(--color-background-subtle);
|
|
969
|
+
border: 1px solid var(--color-border-default);
|
|
970
|
+
border-radius: var(--radius-pill);
|
|
971
|
+
padding: 0 var(--space-2);
|
|
972
|
+
flex-shrink: 0;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.dt.on-inverse .dt-subrow-toggle,
|
|
976
|
+
.dark .dt .dt-subrow-toggle { color: var(--color-foreground-inverse-muted); }
|
|
977
|
+
.dt.on-inverse .dt-subrow-toggle:hover,
|
|
978
|
+
.dark .dt .dt-subrow-toggle:hover {
|
|
979
|
+
background: color-mix(in srgb, var(--color-pure-white) 8%, transparent);
|
|
980
|
+
color: var(--color-foreground-inverse-default);
|
|
981
|
+
}
|
|
982
|
+
.dt.on-inverse .dt-subrow-toggle:focus-visible,
|
|
983
|
+
.dark .dt .dt-subrow-toggle:focus-visible { box-shadow: var(--shadow-focus-inverse); }
|
|
984
|
+
.dt.on-inverse .dt-subrow-count,
|
|
985
|
+
.dark .dt .dt-subrow-count {
|
|
986
|
+
background: transparent;
|
|
987
|
+
border-color: var(--color-border-inverse);
|
|
988
|
+
color: var(--color-foreground-inverse-muted);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
@media (prefers-reduced-motion: reduce) {
|
|
992
|
+
.dt-subrow-inner,
|
|
993
|
+
.dt-subrow-cell,
|
|
994
|
+
.dt-subrow-toggle > i,
|
|
995
|
+
.dt-subrow-toggle > svg { transition: none; }
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/* Mobile: the animation wrapper survives the card layout, but the inner cell
|
|
999
|
+
stacks instead of sitting on one line. */
|
|
1000
|
+
@media (max-width: 900px) {
|
|
1001
|
+
.dt-responsive .dt tbody tr.dt-subrow.is-collapsible > td { padding-top: 0; padding-bottom: 0; }
|
|
1002
|
+
.dt-responsive .dt-subrow-cell { flex-wrap: wrap; padding: var(--space-3) 0; }
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/* ----------------------------------------------------------------
|
|
1006
|
+
12c. ROW AS CONTROL — the whole parent row toggles the group
|
|
1007
|
+
----------------------------------------------------------------
|
|
1008
|
+
With role="button" + tabindex on the <tr>, the row needs what a button has:
|
|
1009
|
+
a pointer, and a visible focus ring. The chevron inside is aria-hidden and
|
|
1010
|
+
pointer-events:none — otherwise one action would take two tab stops and the
|
|
1011
|
+
click would land twice.
|
|
1012
|
+
---------------------------------------------------------------- */
|
|
1013
|
+
.dt tbody tr.has-subrows.is-clickable { cursor: pointer; }
|
|
1014
|
+
.dt tbody tr.has-subrows.is-clickable:focus-visible { outline: none; }
|
|
1015
|
+
.dt tbody tr.has-subrows.is-clickable:focus-visible > td { background: var(--color-background-hover); }
|
|
1016
|
+
.dt tbody tr.has-subrows.is-clickable:focus-visible > td:first-child { box-shadow: inset 3px 0 0 0 var(--color-border-focus); }
|
|
1017
|
+
.dt tbody tr.has-subrows.is-clickable .dt-subrow-toggle { pointer-events: none; }
|
|
1018
|
+
.dt tbody tr.has-subrows.is-clickable[aria-expanded="true"] .dt-subrow-toggle > svg { transform: rotate(90deg); }
|
|
1019
|
+
|
|
1020
|
+
/* ----------------------------------------------------------------
|
|
1021
|
+
12d. DETAIL PANEL — a child that is a DIFFERENT KIND of thing
|
|
1022
|
+
----------------------------------------------------------------
|
|
1023
|
+
The other half of the hierarchy story. Use §12/§12b (.dt-subrow) when the
|
|
1024
|
+
child shares the parent's columns because it is the same kind of thing — a
|
|
1025
|
+
fee under a payment, a leg under a transfer: you want to scan the Amount
|
|
1026
|
+
column and see both. Use THIS when the child has its own attributes and the
|
|
1027
|
+
parent's headers would be lies: an audit trail (timestamp / actor / action),
|
|
1028
|
+
KYC documents, delivery steps.
|
|
1029
|
+
|
|
1030
|
+
Structure:
|
|
1031
|
+
<tr class="has-detail is-clickable" role="button" tabindex="0"
|
|
1032
|
+
aria-expanded="false" aria-controls="d1">
|
|
1033
|
+
<tr class="dt-detail" id="d1">
|
|
1034
|
+
<td colspan="5">
|
|
1035
|
+
<div class="dt-subrow-inner"><div class="dt-detail-body"> … </div></div>
|
|
1036
|
+
|
|
1037
|
+
The reveal mechanism is shared with §12b (the wrapper + max-height written by
|
|
1038
|
+
the handler), so this costs no new animation code. What changes is the cell:
|
|
1039
|
+
one colspan cell instead of one per column, which frees the layout entirely.
|
|
1040
|
+
|
|
1041
|
+
Inside, pick one:
|
|
1042
|
+
.dt-detail-grid key/value pairs — the default for record detail
|
|
1043
|
+
.dt table a nested table WITH ITS OWN <thead>, when children are
|
|
1044
|
+
many and need real column semantics
|
|
1045
|
+
free markup timeline, form, chart
|
|
1046
|
+
---------------------------------------------------------------- */
|
|
1047
|
+
|
|
1048
|
+
.dt tbody tr.dt-detail > td {
|
|
1049
|
+
padding: 0 0 0 calc(var(--space-5) + var(--subrow-indent));
|
|
1050
|
+
background: var(--color-background-subtle);
|
|
1051
|
+
border-bottom: 1px solid var(--color-border-default);
|
|
1052
|
+
}
|
|
1053
|
+
/* Glued to its parent, like a subrow: the pair reads as one block */
|
|
1054
|
+
.dt tbody tr.has-detail.is-open > td { border-bottom: 0; }
|
|
1055
|
+
|
|
1056
|
+
.dt-detail-body {
|
|
1057
|
+
padding: var(--space-4) var(--space-5) var(--space-5) 0;
|
|
1058
|
+
/* Padding starts at zero and arrives with the reveal — same reason as
|
|
1059
|
+
.dt-subrow-cell: a wrapper child's padding floors the collapsed height. */
|
|
1060
|
+
padding: 0;
|
|
1061
|
+
opacity: 0;
|
|
1062
|
+
transition: opacity var(--duration-base) var(--ease-standard);
|
|
1063
|
+
}
|
|
1064
|
+
.dt tbody tr.dt-detail.is-open .dt-detail-body {
|
|
1065
|
+
padding: var(--space-4) var(--space-5) var(--space-5) 0;
|
|
1066
|
+
opacity: 1;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/* Optional heading above the panel content */
|
|
1070
|
+
.dt-detail-title {
|
|
1071
|
+
font-family: var(--font-mono);
|
|
1072
|
+
font-size-adjust: var(--font-mono-adjust);
|
|
1073
|
+
font-size: var(--fs-xxs);
|
|
1074
|
+
font-weight: var(--w-regular);
|
|
1075
|
+
letter-spacing: var(--ls-caps);
|
|
1076
|
+
text-transform: uppercase;
|
|
1077
|
+
color: var(--color-foreground-muted);
|
|
1078
|
+
margin-bottom: var(--space-3);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/* Key/value grid — the default panel content */
|
|
1082
|
+
.dt-detail-grid {
|
|
1083
|
+
display: grid;
|
|
1084
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
1085
|
+
gap: var(--space-4) var(--space-6);
|
|
1086
|
+
}
|
|
1087
|
+
.dt-detail-grid > div { min-width: 0; }
|
|
1088
|
+
.dt-detail-grid dt {
|
|
1089
|
+
font-family: var(--font-mono);
|
|
1090
|
+
font-size-adjust: var(--font-mono-adjust);
|
|
1091
|
+
font-size: var(--fs-xxs);
|
|
1092
|
+
letter-spacing: var(--ls-caps);
|
|
1093
|
+
text-transform: uppercase;
|
|
1094
|
+
color: var(--color-foreground-muted);
|
|
1095
|
+
margin-bottom: 2px;
|
|
1096
|
+
}
|
|
1097
|
+
.dt-detail-grid dd {
|
|
1098
|
+
margin: 0;
|
|
1099
|
+
font-size: var(--fs-sm);
|
|
1100
|
+
color: var(--color-foreground-default);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/* A nested table inside the panel keeps its own headers — that is the whole
|
|
1104
|
+
point of reaching for a panel instead of a subrow. */
|
|
1105
|
+
.dt-detail-body > .dt-wrap { margin: 0; }
|
|
1106
|
+
.dt-detail-body .dt { background: var(--color-background-surface); }
|
|
1107
|
+
|
|
1108
|
+
/* Dark surfaces */
|
|
1109
|
+
.dt.on-inverse tbody tr.dt-detail > td,
|
|
1110
|
+
.dark .dt tbody tr.dt-detail > td {
|
|
1111
|
+
background: color-mix(in srgb, var(--color-pure-white) 4%, transparent);
|
|
1112
|
+
border-bottom-color: var(--color-border-inverse);
|
|
1113
|
+
}
|
|
1114
|
+
.dt.on-inverse .dt-detail-grid dd,
|
|
1115
|
+
.dark .dt .dt-detail-grid dd { color: var(--color-foreground-inverse-default); }
|
|
1116
|
+
.dt.on-inverse .dt-detail-grid dt,
|
|
1117
|
+
.dark .dt .dt-detail-grid dt,
|
|
1118
|
+
.dt.on-inverse .dt-detail-title,
|
|
1119
|
+
.dark .dt .dt-detail-title { color: var(--color-foreground-inverse-muted); }
|
|
1120
|
+
|
|
1121
|
+
/* Row-as-control affordances, shared with §12c */
|
|
1122
|
+
.dt tbody tr.has-detail.is-clickable { cursor: pointer; }
|
|
1123
|
+
.dt tbody tr.has-detail.is-clickable:hover > td { background: var(--color-background-hover); }
|
|
1124
|
+
.dt tbody tr.has-detail.is-clickable:focus-visible { outline: none; }
|
|
1125
|
+
.dt tbody tr.has-detail.is-clickable:focus-visible > td { background: var(--color-background-hover); }
|
|
1126
|
+
.dt tbody tr.has-detail.is-clickable:focus-visible > td:first-child { box-shadow: inset 3px 0 0 0 var(--color-border-focus); }
|
|
1127
|
+
.dt tbody tr.has-detail.is-clickable .dt-subrow-toggle { pointer-events: none; }
|
|
1128
|
+
.dt tbody tr.has-detail.is-clickable[aria-expanded="true"] .dt-subrow-toggle > svg { transform: rotate(90deg); }
|
|
1129
|
+
|
|
1130
|
+
@media (max-width: 900px) {
|
|
1131
|
+
.dt tbody tr.dt-detail > td { padding-left: var(--space-4); }
|
|
1132
|
+
.dt-detail-grid { grid-template-columns: 1fr; }
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
.demo-card-canvas .dt-wrap.dt-responsive { border-radius: var(--shape-container); overflow: hidden; }
|
|
1136
|
+
|
|
1137
|
+
/* A table demo sits on a rounded card, so its wrapper echoes that radius —
|
|
1138
|
+
square corners inside a rounded card read as a box in a box. Last in the
|
|
1139
|
+
sheet so it wins over the base .dt-wrap declaration. */
|
|
1140
|
+
.demo-card-canvas .dt-wrap,
|
|
1141
|
+
.demo-card-canvas .dt-wrap.dt-responsive {
|
|
1142
|
+
border-radius: var(--shape-container);
|
|
1143
|
+
overflow: hidden;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/* ----------------------------------------------------------------
|
|
1147
|
+
DETAIL CARD — a clickable surface for a key-value detail panel
|
|
1148
|
+
----------------------------------------------------------------
|
|
1149
|
+
When the panel holds attributes of one record (not a list), the child is a
|
|
1150
|
+
single object and should look like one: a card, not loose text on the row's
|
|
1151
|
+
background. Clickable when the object has a destination of its own.
|
|
1152
|
+
---------------------------------------------------------------- */
|
|
1153
|
+
.dt-detail-card {
|
|
1154
|
+
display: block;
|
|
1155
|
+
width: 100%;
|
|
1156
|
+
text-align: left;
|
|
1157
|
+
background: var(--color-background-surface);
|
|
1158
|
+
border: var(--border-hairline) solid var(--color-border-default);
|
|
1159
|
+
border-radius: var(--shape-container);
|
|
1160
|
+
padding: var(--space-4) var(--space-5);
|
|
1161
|
+
font: inherit;
|
|
1162
|
+
color: inherit;
|
|
1163
|
+
}
|
|
1164
|
+
button.dt-detail-card,
|
|
1165
|
+
a.dt-detail-card { cursor: pointer; transition: border-color var(--transition-fast), background var(--transition-fast); }
|
|
1166
|
+
button.dt-detail-card:hover,
|
|
1167
|
+
a.dt-detail-card:hover { border-color: var(--color-border-strong); background: var(--color-background-hover); }
|
|
1168
|
+
button.dt-detail-card:focus-visible,
|
|
1169
|
+
a.dt-detail-card:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1170
|
+
|
|
1171
|
+
/* ----------------------------------------------------------------
|
|
1172
|
+
TOOLBAR & PAGINATION SEAM
|
|
1173
|
+
----------------------------------------------------------------
|
|
1174
|
+
The toolbar and the pager are chrome for the SAME surface as the table, not
|
|
1175
|
+
separate blocks stacked on it. A rule under the toolbar (or over the pager)
|
|
1176
|
+
competes with the header row's own bottom border and with the last row's, so
|
|
1177
|
+
the table reads as a third thing floating between two bars. The thead border
|
|
1178
|
+
and the last row's border already mark where data starts and ends.
|
|
1179
|
+
---------------------------------------------------------------- */
|
|
1180
|
+
|
|
1181
|
+
/* ----------------------------------------------------------------
|
|
1182
|
+
TOOLBAR & PAGINATION — the standard
|
|
1183
|
+
----------------------------------------------------------------
|
|
1184
|
+
Both are chrome for the SAME surface as the table and live INSIDE .dt-wrap.
|
|
1185
|
+
Parked as a canvas sibling they float above the table with a dangling border.
|
|
1186
|
+
|
|
1187
|
+
Toolbar, three zones:
|
|
1188
|
+
.dt-toolbar-lead identity — title + count. State whether the count is
|
|
1189
|
+
total or filtered ("12 of 248"), or it lies under filter.
|
|
1190
|
+
.dt-toolbar-filters search + chips; takes the middle
|
|
1191
|
+
.dt-toolbar-actions margin-left:auto — export, columns, density.
|
|
1192
|
+
Never a flex:1 spacer: a spacer competing with the
|
|
1193
|
+
lead collapses it (same bug as the ActionBar).
|
|
1194
|
+
|
|
1195
|
+
Selection mode (.is-selecting) is the same bar in another state, not a new
|
|
1196
|
+
bar: the lead becomes "3 selected" and the actions become bulk actions. The
|
|
1197
|
+
height is preserved so the table does not jump when a row is ticked.
|
|
1198
|
+
|
|
1199
|
+
Footer is pagination only. Rows-per-page sits with the range, not in the
|
|
1200
|
+
toolbar: it describes the page, and the toolbar describes the data.
|
|
1201
|
+
---------------------------------------------------------------- */
|
|
1202
|
+
.dt-wrap > .dt-toolbar {
|
|
1203
|
+
display: flex;
|
|
1204
|
+
align-items: center;
|
|
1205
|
+
gap: var(--space-4);
|
|
1206
|
+
height: 52px;
|
|
1207
|
+
padding: 0 var(--space-5);
|
|
1208
|
+
box-sizing: border-box;
|
|
1209
|
+
}
|
|
1210
|
+
.dt-toolbar.dt-toolbar-sm { height: 44px; padding: 0 var(--space-4); }
|
|
1211
|
+
.dt-toolbar-lead { display: inline-flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
|
|
1212
|
+
.dt-toolbar-filters { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; flex: 1 1 auto; }
|
|
1213
|
+
.dt-toolbar-actions { margin-left: auto; display: inline-flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }
|
|
1214
|
+
/* Selection mode keeps the toolbar on the surface colour. The tint marks WHAT
|
|
1215
|
+
is selected; repeating it on the chrome dilutes that signal and the bar stops
|
|
1216
|
+
reading as chrome at all. The state is carried by content instead — the count
|
|
1217
|
+
in brand, and bulk actions replacing the table actions. */
|
|
1218
|
+
.dt-toolbar.is-selecting .dt-toolbar-lead {
|
|
1219
|
+
color: var(--color-foreground-brand);
|
|
1220
|
+
font-weight: var(--w-bold);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.dt-wrap > .dt-pagination {
|
|
1224
|
+
display: flex;
|
|
1225
|
+
align-items: center;
|
|
1226
|
+
gap: var(--space-4);
|
|
1227
|
+
height: 44px;
|
|
1228
|
+
padding: 0 var(--space-5);
|
|
1229
|
+
box-sizing: border-box;
|
|
1230
|
+
}
|
|
1231
|
+
.dt-pagination-range { color: var(--color-foreground-muted); }
|
|
1232
|
+
.dt-pagination-size { display: inline-flex; align-items: center; gap: var(--space-2); }
|
|
1233
|
+
/* RIGHT BY DEFAULT — the PAGER goes to the edge; the range and the rows-per-page
|
|
1234
|
+
control stay together on the left, which is the reading order: what you are
|
|
1235
|
+
looking at, then how much of it, then where to go next.
|
|
1236
|
+
The earlier version pushed the last child whatever it was, so a footer with no
|
|
1237
|
+
pager sent its own range to the right. Anchoring on the nav keeps the intent,
|
|
1238
|
+
and the fallback below covers a caller who writes the pager with own markup. */
|
|
1239
|
+
.dt-pagination > nav:last-child,
|
|
1240
|
+
.dt-pagination > .pager:last-child { margin-left: auto; }
|
|
1241
|
+
.dt-pagination-nav { margin-left: auto; display: inline-flex; align-items: center; gap: var(--space-1); }
|
|
1242
|
+
|
|
1243
|
+
/* .scroll-x creates its own scroll context, so the wrapper's overflow:hidden
|
|
1244
|
+
cannot clip it and the wrapper's rounded corners show THROUGH the table.
|
|
1245
|
+
Give the scroller the same radius on the corners it touches: top corners when
|
|
1246
|
+
it follows a toolbar, bottom when a pager follows it, all four when alone. */
|
|
1247
|
+
.dt-wrap > .scroll-x { border-radius: inherit; }
|
|
1248
|
+
.dt-wrap > .dt-toolbar + .scroll-x { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
1249
|
+
.dt-wrap > .scroll-x:has(+ .dt-pagination) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
|
1250
|
+
|
|
1251
|
+
/* Vertical scroller: the wrapper cannot clip a descendant that scrolls, so its
|
|
1252
|
+
rounded corners vanish behind the scrollbar gutter. The scroller carries the
|
|
1253
|
+
radius on the edges it owns instead. */
|
|
1254
|
+
.dt-wrap > .dt-scroll-y,
|
|
1255
|
+
.dt-wrap > .scroll-x { border-radius: inherit; }
|
|
1256
|
+
.dt-wrap > .scroll-x { overflow-x: auto; overflow-y: hidden; }
|
|
1257
|
+
.dt-wrap > .dt-scroll-y { overflow-y: auto; overflow-x: hidden; }
|
|
1258
|
+
/* Any hand-rolled vertical scroller in a wrap gets the same treatment */
|
|
1259
|
+
.dt-wrap > [style*="overflow-y"] { border-radius: inherit; }
|
|
1260
|
+
.dt-wrap > .dt-toolbar + [style*="overflow-y"] { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
1261
|
+
.dt-wrap > [style*="overflow-y"]:has(+ .dt-pagination) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
|
1262
|
+
.dt-wrap > .dt-toolbar + .dt-scroll-y { border-top-left-radius: 0; border-top-right-radius: 0; }
|
|
1263
|
+
.dt-wrap > .dt-scroll-y:has(+ .dt-pagination) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
|
|
1264
|
+
|
|
1265
|
+
/* ----------------------------------------------------------------
|
|
1266
|
+
FRAME OWNERSHIP — the wrap only draws a border when nothing else bounds it
|
|
1267
|
+
----------------------------------------------------------------
|
|
1268
|
+
.dt-wrap's border says "the table starts and ends here". A toolbar closes the
|
|
1269
|
+
top, a pager closes the bottom, and a card/drawer closes all four — in those
|
|
1270
|
+
cases the border is a second contour around a boundary that already reads.
|
|
1271
|
+
Radius stays either way: it is what clips the thead corners.
|
|
1272
|
+
---------------------------------------------------------------- */
|
|
1273
|
+
.dt-wrap:has(> .dt-toolbar),
|
|
1274
|
+
.dt-wrap:has(> .dt-pagination) { border: 0; }
|
|
1275
|
+
/* Chrome carries the edge instead */
|
|
1276
|
+
.dt-wrap:has(> .dt-toolbar) > .dt-toolbar { border-top: var(--border-hairline) solid var(--color-border-default); }
|
|
1277
|
+
.dt-wrap:has(> .dt-pagination) > .dt-pagination { border-bottom: var(--border-hairline) solid var(--color-border-default); }
|
|
1278
|
+
/* Inside a card, drawer or modal the container is the frame */
|
|
1279
|
+
.demo-card-canvas .dt-wrap,
|
|
1280
|
+
.drawer-body .dt-wrap,
|
|
1281
|
+
.modal-body .dt-wrap { border: 0; }
|
|
1282
|
+
|
|
1283
|
+
/* Checkbox column — fixed narrow gutter so the first data column starts at the
|
|
1284
|
+
same x whether or not selection is on. */
|
|
1285
|
+
.dt .dt-check { width: 40px; padding-right: 0; }
|
|
1286
|
+
.dt .dt-check .check { margin: 0; }
|
|
1287
|
+
|
|
1288
|
+
/* Size modifiers — the row scale. Restored after the dt-sm/-md/-lg alias cleanup
|
|
1289
|
+
removed the only block that declared them. */
|
|
1290
|
+
.dt.dt-sm { font-size: var(--fs-xs); }
|
|
1291
|
+
.dt.dt-md { font-size: var(--fs-sm); }
|
|
1292
|
+
.dt.dt-lg { font-size: var(--fs-base); }
|
|
1293
|
+
|
|
1294
|
+
/* ----------------------------------------------------------------
|
|
1295
|
+
VOID FIGURES — a number that no longer counts
|
|
1296
|
+
----------------------------------------------------------------
|
|
1297
|
+
A cancelled, reversed or voided amount is still worth showing: the reader
|
|
1298
|
+
needs to know the transaction existed. Struck through says "this figure does
|
|
1299
|
+
not count" in a way dimming alone cannot — muted grey reads as "secondary",
|
|
1300
|
+
not as "annulled", and in a money column that difference matters.
|
|
1301
|
+
|
|
1302
|
+
Only figures are struck. Running the strike across a whole row would cross
|
|
1303
|
+
out names and references too, which are still true; it is the VALUE that was
|
|
1304
|
+
voided.
|
|
1305
|
+
|
|
1306
|
+
Applies automatically inside a muted row (the state that already means
|
|
1307
|
+
cancelled or returned), and .is-void marks a single cell when the row itself
|
|
1308
|
+
is still live.
|
|
1309
|
+
---------------------------------------------------------------- */
|
|
1310
|
+
.dt tbody tr.is-muted td.dt-num,
|
|
1311
|
+
.dt tbody td.dt-num.is-void {
|
|
1312
|
+
text-decoration: line-through;
|
|
1313
|
+
/* Hairline: the strike marks the figure as annulled, it does not cross it out.
|
|
1314
|
+
At 1.5px over tabular mono bold the rule competes with the digits and the
|
|
1315
|
+
amount stops being readable — which defeats the point of still showing it. */
|
|
1316
|
+
text-decoration-thickness: var(--border-hairline);
|
|
1317
|
+
text-decoration-color: var(--color-foreground-muted);
|
|
1318
|
+
color: var(--color-foreground-secondary);
|
|
1319
|
+
}
|
|
1320
|
+
/* The currency chip is not part of the figure — striking it looks like damage */
|
|
1321
|
+
.dt tbody tr.is-muted td.dt-num .ccy,
|
|
1322
|
+
.dt tbody td.dt-num.is-void .ccy { text-decoration: none; }
|
|
1323
|
+
.dt.on-inverse tbody tr.is-muted td.dt-num,
|
|
1324
|
+
.dt.on-inverse tbody td.dt-num.is-void,
|
|
1325
|
+
.dark .dt tbody tr.is-muted td.dt-num,
|
|
1326
|
+
.dark .dt tbody td.dt-num.is-void {
|
|
1327
|
+
text-decoration-color: var(--color-foreground-inverse-muted);
|
|
1328
|
+
color: var(--color-foreground-inverse-secondary);
|
|
1329
|
+
}
|