@webority/theme 0.3.0 → 0.5.0

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.
@@ -0,0 +1,614 @@
1
+ // Empty state, base table, ledger table + pagination.
2
+ //
3
+ // Part of the wui-components layer — see _wui-components.scss for the whole
4
+ // picture and the load order. Order within the layer is LOAD-BEARING: these
5
+ // partials are a split of one stylesheet, not independent modules, so a rule
6
+ // here may be overridden by a later partial. Never reorder the imports.
7
+
8
+ // ── Empty state ──
9
+ .empty-state {
10
+ text-align: center;
11
+ padding: 48px 24px;
12
+ color: var(--color-text-subtle);
13
+ }
14
+ .empty-state-title {
15
+ font-size: 16px;
16
+ font-weight: 600;
17
+ line-height: 1.35;
18
+ color: var(--color-text);
19
+ }
20
+ .empty-state-message {
21
+ margin: 6px 0 0;
22
+ font-size: 14px;
23
+ line-height: 1.5;
24
+ color: var(--color-text-muted);
25
+ }
26
+ .empty-state-action {
27
+ margin-top: 16px;
28
+ }
29
+ // Tighter scale for a widget/tile, as opposed to a full card or page.
30
+ .empty-state-sm {
31
+ padding: 28px 16px;
32
+ }
33
+ .empty-state-sm .empty-state-title {
34
+ font-size: 14px;
35
+ }
36
+ .empty-state-sm .empty-state-message {
37
+ font-size: 12px;
38
+ }
39
+ .empty-state-icon {
40
+ width: 48px;
41
+ height: 48px;
42
+ border-radius: var(--radius-lg);
43
+ background: var(--color-primary-soft);
44
+ color: var(--color-primary);
45
+ display: inline-flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ margin-bottom: 14px;
49
+ }
50
+
51
+ // The empty state a DataTable renders in place of its rows. Distinct from
52
+ // the card-level .empty-state above: a 50px primary-tinted disc, then a 32px
53
+ // gap to the title and 12px to the subtitle.
54
+ .tbl-empty {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: center;
58
+ text-align: center;
59
+ padding: 40px 24px;
60
+ }
61
+ .tbl-empty-icon {
62
+ display: inline-grid;
63
+ place-items: center;
64
+ flex: none;
65
+ width: 50px;
66
+ height: 50px;
67
+ border-radius: 50%;
68
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
69
+ color: var(--color-primary);
70
+ margin-bottom: 16px;
71
+ }
72
+ .tbl-empty-title {
73
+ font-size: 18px;
74
+ font-weight: 600;
75
+ line-height: 1.35;
76
+ color: var(--color-text);
77
+ }
78
+ .tbl-empty-text {
79
+ margin-top: 8px;
80
+ font-size: 16px;
81
+ font-weight: 400;
82
+ line-height: 1.5;
83
+ color: var(--color-text-muted);
84
+ }
85
+ // An action (e.g. "New assessment") sits under the subtitle when the caller
86
+ // supplies one.
87
+ .tbl-empty-action {
88
+ margin-top: 20px;
89
+ }
90
+
91
+ .lx-table-empty {
92
+ padding: 36px 24px;
93
+ text-align: center;
94
+ color: var(--color-text-muted);
95
+ }
96
+ // The empty state owns its own padding and is not a row you can act on — no
97
+ // cell padding, no border, and no hover fill from .table / .table-ledger.
98
+ .table td.lx-table-empty:has(> .tbl-empty) {
99
+ padding: 0;
100
+ border-bottom: 0;
101
+ }
102
+ .table tbody tr:hover td.lx-table-empty:has(> .tbl-empty),
103
+ .table-ledger tbody tr:hover td.lx-table-empty:has(> .tbl-empty) {
104
+ background: transparent;
105
+ }
106
+
107
+ // ── Table (base) ──
108
+ // The shared data-table look, on the plain `.table` class Bootstrap already
109
+ // gives every table. Without this a product's tables fall back to stock
110
+ // Bootstrap and drift from the design system; `.table-ledger` below is the
111
+ // denser, card-inset variant, not a replacement for having a base.
112
+ .table {
113
+ --bs-table-bg: transparent;
114
+ --bs-table-color: var(--color-text);
115
+ width: 100%;
116
+ margin-bottom: 0;
117
+ color: var(--color-text);
118
+ vertical-align: middle;
119
+ border-color: var(--color-border);
120
+ // `separate` is required for a cell to render a border-radius at all —
121
+ // Bootstrap reboot sets `collapse`, under which .table-ledger's rounded
122
+ // header corners silently do nothing. border-spacing 0 keeps cells flush.
123
+ border-collapse: separate;
124
+ border-spacing: 0;
125
+ }
126
+
127
+ // The cell rules are wrapped in :where() so they carry ZERO class specificity
128
+ // — `:where(.table) > thead > tr > th` is (0,0,3), which any single-class
129
+ // variant selector such as `.table-ledger thead th` (0,1,2) outranks.
130
+ //
131
+ // Without this the base out-ranks its own variant: written plainly,
132
+ // `.table > thead > tr > th` is (0,1,3) and beats `.table-ledger thead th`,
133
+ // so the ledger lost its own border-bottom/line-height/font-family to the
134
+ // base. A base layer must always sit *under* the variants built on it.
135
+ //
136
+ // The catch: zero class specificity also loses to BOOTSTRAP. Its
137
+ // `.table > :not(caption) > * > *` is (0,1,1) and sets `padding`, `color` and
138
+ // `background-color` on every cell, so declaring those three here would be dead
139
+ // CSS — measured: they never applied. Each therefore goes through the channel
140
+ // Bootstrap itself reads:
141
+ // padding -> $table-cell-padding-y/x, overridden before bootstrap loads
142
+ // colour -> --bs-table-color (its fallback in the color-state/type chain)
143
+ // background -> --bs-table-bg
144
+ // A custom property declared on the cell beats the one `.table` passes down to it,
145
+ // so these land without out-ranking anything — the same move as the
146
+ // `--bs-table-bg-state` hover below, and the reason that one already works.
147
+ :where(.table) > thead > tr > th {
148
+ --bs-table-bg: var(--color-bg-muted);
149
+ --bs-table-color: var(--color-text-subtle);
150
+ height: 42px;
151
+ // Only the horizontal inset differs from the shared cell padding.
152
+ padding-inline: 12px;
153
+ // `height` is only a MINIMUM on a table cell, so the header lands on 42px only
154
+ // while its content box stays under that; left to the font's own metrics it
155
+ // drifted to 43.6px. Pinning the line box to the font size keeps
156
+ // 12 + 12 + 12 + 1px border comfortably under 42, so the minimum governs and the
157
+ // band is exactly 42px — and it stays 42px if the shared cell padding changes,
158
+ // which a hardcoded line-height would not. Bootstrap's cell rule does not set
159
+ // line-height, so this one still reaches the element.
160
+ line-height: 1;
161
+ border-bottom: 1px solid var(--color-border);
162
+ font-size: 12px;
163
+ font-weight: 500;
164
+ letter-spacing: 0.04em;
165
+ text-transform: uppercase;
166
+ white-space: nowrap;
167
+ vertical-align: middle;
168
+ transition:
169
+ background-color 0.12s ease,
170
+ color 0.12s ease;
171
+ }
172
+ :where(.table) > tbody > tr > td {
173
+ --bs-table-color: var(--color-text-muted);
174
+ border-bottom: 1px solid var(--color-border);
175
+ font-size: 14px;
176
+ vertical-align: middle;
177
+ transition: background-color 0.12s ease;
178
+ }
179
+ :where(.table) > tbody > tr:last-child > td {
180
+ border-bottom: 0;
181
+ }
182
+ :where(.table) > tbody > tr:hover {
183
+ --bs-table-bg-state: var(--color-bg-subtle);
184
+ }
185
+ @media (prefers-reduced-motion: reduce) {
186
+ :where(.table) > thead > tr > th,
187
+ :where(.table) > tbody > tr > td {
188
+ transition: none;
189
+ }
190
+ }
191
+ // Numeric / action columns align to the trailing edge.
192
+ .table .cell-end,
193
+ .table thead th.cell-end {
194
+ text-align: right;
195
+ }
196
+
197
+ // ── Table + pagination ──
198
+ // DataTable cell helpers
199
+ .table-ledger .lx-cell-end,
200
+ .table-ledger thead th.lx-cell-end {
201
+ text-align: right;
202
+ }
203
+
204
+ // Ledger chrome — the shared table look. Applies to every .table-ledger: a
205
+ // floating 42px header band, airy rows on a hairline rule, and an inset from
206
+ // the card edge. Column geometry is NOT set here.
207
+ .table-wrap:has(> .table-ledger) {
208
+ padding: 20px 20px 0;
209
+ }
210
+ .table-ledger {
211
+ border-spacing: 0;
212
+ }
213
+ .table-ledger thead th {
214
+ height: 42px;
215
+ padding: 0 12px;
216
+ background: var(--color-bg-muted);
217
+ border-bottom: none;
218
+ font-family: "Inter", var(--font-sans, sans-serif);
219
+ font-size: 12px;
220
+ font-weight: 500;
221
+ line-height: 17px;
222
+ letter-spacing: 0.04em;
223
+ text-transform: uppercase;
224
+ // --color-text-subtle (#78829D) is ~3.5:1 on the muted band, below WCAG AA's 4.5:1
225
+ // for 12px text. A column header names the data under it — that is not decorative
226
+ // text, so it takes the muted ramp (~7.5:1). Caught by the axe pass, on both surfaces.
227
+ color: var(--color-text-muted);
228
+ vertical-align: middle;
229
+ }
230
+ .table-ledger thead th:first-child {
231
+ padding-left: 20px;
232
+ border-radius: var(--radius-sm) 0 0 var(--radius-sm);
233
+ }
234
+ .table-ledger thead th:last-child {
235
+ padding-right: 20px;
236
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
237
+ }
238
+ .table-ledger tbody td {
239
+ height: 54px;
240
+ padding: 12px;
241
+ border-bottom: 0.5px solid var(--color-border);
242
+ font-family: "Inter", var(--font-sans, sans-serif);
243
+ font-size: 12px;
244
+ font-weight: 500;
245
+ line-height: 15px;
246
+ color: var(--color-text-muted);
247
+ }
248
+ .table-ledger tbody td:first-child {
249
+ padding-left: 20px;
250
+ }
251
+ .table-ledger tbody td:last-child {
252
+ padding-right: 20px;
253
+ }
254
+ .table-ledger tbody tr:last-child td {
255
+ border-bottom: none;
256
+ }
257
+ .table-ledger tbody tr:hover {
258
+ --bs-table-bg-state: var(--color-bg-subtle);
259
+ }
260
+ .table-ledger .u-meta {
261
+ gap: 2px;
262
+ }
263
+ .table-ledger .u-name {
264
+ font-size: 14px;
265
+ font-weight: 500;
266
+ line-height: 19px;
267
+ color: var(--color-text);
268
+ }
269
+ @media (max-width: 575.98px) {
270
+ .table-ledger .u-name {
271
+ font-size: 12px;
272
+ line-height: normal;
273
+ margin-bottom: 2px;
274
+ }
275
+ }
276
+ // An identity-cell name is often a link, so it inherits Bootstrap's
277
+ // underline-on-hover. The colour shift is the affordance here.
278
+ .table-ledger a.u-name,
279
+ .table-ledger a.u-name:hover {
280
+ text-decoration: none;
281
+ }
282
+ // Identity-cell subline. `.u-email` is in the group because an identity cell
283
+ // renders the secondary line as either a generic .lx-text-xs or a .u-email.
284
+ .table-ledger .u-meta > .u-email,
285
+ .table-ledger .u-meta > .lx-text-xs {
286
+ font-size: 12px;
287
+ font-weight: 400;
288
+ line-height: 15px;
289
+ color: var(--color-text-subtle);
290
+ }
291
+ .table-ledger .u-row-actions {
292
+ gap: 6px;
293
+ }
294
+
295
+ // Ledger tables: 20px edge padding so header labels + first/last cells align
296
+ // with the card edge — shared across every .table-ledger surface.
297
+ .table-ledger thead th:first-child,
298
+ .table-ledger tbody td:first-child {
299
+ padding-left: 20px;
300
+ }
301
+ .table-ledger thead th:last-child,
302
+ .table-ledger tbody td:last-child {
303
+ padding-right: 20px;
304
+ }
305
+
306
+ // Pagination footer — rows-per-page + range + prev/next, sits flush under a
307
+ // table.
308
+ .table-pagination {
309
+ display: flex;
310
+ align-items: center;
311
+ justify-content: flex-end;
312
+ gap: 22px;
313
+ padding: 12px 16px;
314
+ border-top: 1px solid var(--color-border);
315
+ font-size: 13px;
316
+ color: var(--color-text-muted);
317
+ flex-wrap: wrap;
318
+ }
319
+ // Rows-per-page sits at the leading edge; range + nav stay grouped on the
320
+ // right.
321
+ .tpg-rows {
322
+ display: inline-flex;
323
+ align-items: center;
324
+ gap: 8px;
325
+ white-space: nowrap;
326
+ margin-right: auto;
327
+ font-size: 12px;
328
+ font-weight: 500;
329
+ text-transform: uppercase;
330
+ color: var(--color-text-subtle);
331
+ }
332
+ .select.tpg-select {
333
+ width: auto;
334
+ min-width: 64px;
335
+ height: 32px;
336
+ min-height: 32px;
337
+ padding: 0 28px 0 12px;
338
+ font-size: 13px;
339
+ font-weight: 600;
340
+ border-radius: var(--radius);
341
+ background-position: right 8px center;
342
+ background-size: 14px;
343
+ }
344
+ .tpg-range {
345
+ font-variant-numeric: tabular-nums;
346
+ white-space: nowrap;
347
+ font-size: 14px;
348
+ font-weight: 500;
349
+ color: var(--color-text-muted);
350
+ }
351
+ .tpg-nav {
352
+ display: inline-flex;
353
+ align-items: center;
354
+ gap: 4px;
355
+ }
356
+ .tpg-arrow {
357
+ display: grid;
358
+ place-items: center;
359
+ width: 32px;
360
+ height: 32px;
361
+ border: none;
362
+ background: transparent;
363
+ color: var(--color-text-muted);
364
+ border-radius: 50%;
365
+ cursor: pointer;
366
+ transition:
367
+ background 0.14s ease,
368
+ color 0.14s ease;
369
+ }
370
+ .tpg-arrow:hover:not(:disabled) {
371
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
372
+ color: var(--color-primary);
373
+ }
374
+ // Text-5 already reads as "unavailable" — no opacity dimming on top of it.
375
+ .tpg-arrow:disabled {
376
+ color: var(--color-border-strong);
377
+ cursor: not-allowed;
378
+ }
379
+
380
+ // Horizontal scroll guard + flush-fit inside a card. Wrapping the table here
381
+ // also stops `.card > .table` header-corner rounding from applying mid-card.
382
+ .table-wrap {
383
+ overflow-x: auto;
384
+ max-width: 100%;
385
+ // Keep the horizontal swipe inside the table — don't let it drag the
386
+ // page — and give touch devices momentum scrolling. Scrollbar is
387
+ // thinned/hidden so the scroll feels native.
388
+ overscroll-behavior-x: contain;
389
+ -webkit-overflow-scrolling: touch;
390
+ scrollbar-width: thin;
391
+ scrollbar-color: var(--color-border-strong) transparent;
392
+ }
393
+ .table-wrap::-webkit-scrollbar {
394
+ height: 6px;
395
+ }
396
+ .table-wrap::-webkit-scrollbar-track {
397
+ background: transparent;
398
+ }
399
+ .table-wrap::-webkit-scrollbar-thumb {
400
+ background: var(--color-border-strong);
401
+ border-radius: 999px;
402
+ }
403
+
404
+ // Row-selection checkbox column.
405
+ .table-ledger th.lx-select-cell,
406
+ .table-ledger td.lx-select-cell {
407
+ width: 44px;
408
+ min-width: 44px;
409
+ max-width: 44px;
410
+ text-align: center;
411
+ padding-left: 12px;
412
+ padding-right: 0;
413
+ }
414
+ // Row state goes through Bootstrap's own --bs-table-bg-state, NOT `background`.
415
+ // Bootstrap 5.3 paints cell state with `box-shadow: inset 0 0 0 9999px
416
+ // var(--bs-table-bg-state, …)`, which covers whatever background the cell has —
417
+ // so setting `background` on the <tr> OR the <td> is invisible. Setting the
418
+ // variable on the row is what `.table-active` itself does, and it inherits to
419
+ // every cell. Brand tint, not grey: grey reads as "disabled", not "chosen".
420
+ .table-ledger tbody tr.is-selected {
421
+ --bs-table-bg-state: var(--color-primary-soft);
422
+ }
423
+ .table-ledger tbody tr.is-selected:hover {
424
+ --bs-table-bg-state: color-mix(in srgb, var(--color-primary) 14%, transparent);
425
+ }
426
+
427
+ // Row status affordance — a colour bar on the leading edge, so a row's state is
428
+ // legible without relying on a tint alone.
429
+ .table-ledger tbody tr[data-status] > td:first-child {
430
+ box-shadow: inset 3px 0 0 0 var(--color-border-strong);
431
+ }
432
+ .table-ledger tbody tr[data-status="success"] > td:first-child {
433
+ box-shadow: inset 3px 0 0 0 var(--color-success);
434
+ }
435
+ .table-ledger tbody tr[data-status="warning"] > td:first-child {
436
+ box-shadow: inset 3px 0 0 0 var(--color-warning);
437
+ }
438
+ .table-ledger tbody tr[data-status="danger"] > td:first-child {
439
+ box-shadow: inset 3px 0 0 0 var(--color-danger);
440
+ }
441
+ .table-ledger tbody tr[data-status="info"] > td:first-child {
442
+ box-shadow: inset 3px 0 0 0 var(--color-info);
443
+ }
444
+
445
+ // Row actions — icon-only buttons replacing a kebab dropdown.
446
+ .u-row-actions {
447
+ display: flex;
448
+ align-items: center;
449
+ justify-content: flex-end;
450
+ gap: 6px;
451
+ // Action cells never wrap — a status hint stays on one line.
452
+ white-space: nowrap;
453
+ }
454
+ .u-action {
455
+ display: inline-grid;
456
+ place-items: center;
457
+ flex: none;
458
+ width: 36px;
459
+ height: 36px;
460
+ padding: 0;
461
+ border: none;
462
+ background: var(--color-bg-subtle);
463
+ border-radius: 999px;
464
+ color: var(--color-text-subtle);
465
+ cursor: pointer;
466
+ transition:
467
+ background 0.12s ease,
468
+ color 0.12s ease;
469
+ position: relative;
470
+ }
471
+ // Touch pointers need a 44px minimum target. Grow the hit area, not the
472
+ // ink, so row density and alignment are untouched.
473
+ @media (pointer: coarse) {
474
+ .u-action::after {
475
+ content: "";
476
+ position: absolute;
477
+ top: 50%;
478
+ left: 50%;
479
+ transform: translate(-50%, -50%);
480
+ width: 44px;
481
+ height: 44px;
482
+ }
483
+ }
484
+ .u-action svg {
485
+ flex: none;
486
+ }
487
+ .u-action:hover:not(:disabled) {
488
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
489
+ color: var(--color-primary);
490
+ }
491
+ .u-action:disabled {
492
+ opacity: 0.4;
493
+ cursor: not-allowed;
494
+ }
495
+ .u-action.danger {
496
+ color: var(--color-danger-text);
497
+ }
498
+ .u-action.danger:hover:not(:disabled) {
499
+ background: color-mix(in srgb, var(--color-danger) 10%, transparent);
500
+ color: var(--color-danger-text);
501
+ }
502
+ .u-action.u-action-preview {
503
+ background: var(--color-primary-soft);
504
+ color: var(--color-primary);
505
+ }
506
+ .u-action.u-action-download {
507
+ background: var(--color-success-soft);
508
+ color: var(--color-success-text);
509
+ }
510
+ .u-action.u-action-share {
511
+ background: var(--color-warning-soft);
512
+ color: var(--color-warning-text);
513
+ }
514
+ .u-action.u-action-preview:hover:not(:disabled) {
515
+ background: var(--color-primary-soft);
516
+ color: var(--color-primary);
517
+ }
518
+ .u-action.u-action-download:hover:not(:disabled) {
519
+ background: var(--color-success-soft);
520
+ color: var(--color-success-text);
521
+ }
522
+ .u-action.u-action-share:hover:not(:disabled) {
523
+ background: var(--color-warning-soft);
524
+ color: var(--color-warning-text);
525
+ }
526
+ .u-action.u-action-danger:hover {
527
+ color: var(--color-danger-text);
528
+ background: color-mix(in srgb, var(--color-danger) 10%, transparent);
529
+ }
530
+
531
+ // Identity cell — avatar + stacked name/email.
532
+ .u-cell {
533
+ display: flex;
534
+ align-items: center;
535
+ gap: 14px;
536
+ }
537
+ .u-avatar {
538
+ width: 40px;
539
+ height: 40px;
540
+ border-radius: 50%;
541
+ flex: none;
542
+ display: grid;
543
+ place-items: center;
544
+ font-size: 16px;
545
+ font-weight: 600;
546
+ color: var(--color-primary);
547
+ background: color-mix(in srgb, var(--color-primary) 20%, transparent);
548
+ }
549
+ .u-meta {
550
+ display: flex;
551
+ flex-direction: column;
552
+ min-width: 0;
553
+ }
554
+ .u-name {
555
+ display: inline-flex;
556
+ align-items: center;
557
+ gap: 8px;
558
+ font-size: 14px;
559
+ font-weight: 600;
560
+ color: var(--color-text);
561
+ }
562
+ .u-email {
563
+ font-size: 14px;
564
+ font-weight: 400;
565
+ color: var(--color-text-muted);
566
+ }
567
+ .u-you {
568
+ padding: 1px 7px;
569
+ font-size: 10.5px;
570
+ }
571
+
572
+ // Table extras — a sortable header and numeric cells. These sit AFTER the
573
+ // .table-ledger rules above on purpose: they are opt-in additions on top of
574
+ // whichever table variant is in use, not part of the base a variant overrides.
575
+ .table th.sortable {
576
+ display: flex;
577
+ align-items: center;
578
+ gap: 6px;
579
+ cursor: pointer;
580
+ user-select: none;
581
+ }
582
+ .table th.sortable .sort-icon {
583
+ // Reserved space so the header does not jump width when the arrow appears.
584
+ width: 12px;
585
+ flex: none;
586
+ color: var(--color-text-subtle);
587
+ opacity: 0;
588
+ transition: opacity 0.12s ease;
589
+ }
590
+ .table th.sortable:hover .sort-icon {
591
+ opacity: 0.6;
592
+ }
593
+ .table th.sortable[aria-sort="ascending"] .sort-icon,
594
+ .table th.sortable[aria-sort="descending"] .sort-icon {
595
+ opacity: 1;
596
+ color: var(--color-primary);
597
+ }
598
+ .table th.sortable[aria-sort="descending"] .sort-icon {
599
+ transform: rotate(180deg);
600
+ }
601
+ .table th.sortable[aria-sort="ascending"],
602
+ .table th.sortable[aria-sort="descending"] {
603
+ color: var(--color-text);
604
+ }
605
+
606
+ // Numeric cells: right-aligned with tabular figures, so digits line up in a column
607
+ // and a total sits directly under the values it sums.
608
+ .table th.num,
609
+ .table td.num {
610
+ text-align: right;
611
+ font-variant-numeric: tabular-nums;
612
+ }
613
+
614
+ // ============================================================================