@crewlethq/ui 0.2.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,1593 @@
1
+ /* ─────────────────────────────────────────────────────────────────
2
+ * Default variant.
3
+ *
4
+ * Every rule is scoped under the variant root, so the table's
5
+ * internals never match an application's own class names and the
6
+ * compact variant's rules further down never leak into this one.
7
+ * ───────────────────────────────────────────────────────────────── */
8
+ .crewlet-data-table--default {
9
+ width: 100%;
10
+ position: relative;
11
+ overflow-x: auto;
12
+ }
13
+
14
+ .crewlet-data-table--default .crewlet-data-table__table {
15
+ width: 100%;
16
+ border-collapse: collapse;
17
+ background: transparent;
18
+ display: table;
19
+ table-layout: auto;
20
+ }
21
+
22
+ .crewlet-data-table--default .crewlet-data-table__table thead {
23
+ border-bottom: 1px solid var(--color-border-default);
24
+ display: table-header-group;
25
+ }
26
+
27
+ .crewlet-data-table--default .crewlet-data-table__table tbody {
28
+ display: table-row-group;
29
+ }
30
+
31
+ .crewlet-data-table--default .crewlet-data-table__table tr {
32
+ display: table-row;
33
+ }
34
+
35
+ /*
36
+ * Header cells hold the absolutely positioned resize handle, so they
37
+ * are the positioning context, and the right padding is kept tight so
38
+ * the handle sits against the label rather than a gutter.
39
+ */
40
+ .crewlet-data-table--default .crewlet-data-table__table th {
41
+ padding: 2px 4px 2px 16px;
42
+ text-align: left;
43
+ font-size: 12px;
44
+ font-weight: 600;
45
+ color: var(--color-text-secondary);
46
+ text-transform: uppercase;
47
+ letter-spacing: 0.5px;
48
+ position: relative;
49
+ user-select: none;
50
+ display: table-cell;
51
+ white-space: nowrap;
52
+ }
53
+
54
+ .crewlet-data-table--default .crewlet-data-table__table td {
55
+ padding: 12px 16px;
56
+ font-size: 14px;
57
+ color: var(--color-text-primary);
58
+ border-bottom: 1px solid var(--color-border-default);
59
+ display: table-cell;
60
+ text-align: left;
61
+ vertical-align: middle;
62
+ white-space: nowrap;
63
+ }
64
+
65
+ /*
66
+ * Column separator on the header row only. The th already declares
67
+ * position: relative for the resize handle, so the pseudo can hang
68
+ * off the right edge without extra wrapping.
69
+ */
70
+ .crewlet-data-table--default .crewlet-data-table__table thead th:not(:last-child)::after {
71
+ content: '';
72
+ position: absolute;
73
+ top: 6px;
74
+ bottom: 6px;
75
+ right: 0;
76
+ width: 1px;
77
+ background: var(--color-border-default);
78
+ pointer-events: none;
79
+ }
80
+
81
+ .crewlet-data-table--default .crewlet-data-table__table tbody tr {
82
+ background: var(--color-surface-subtle);
83
+ transition: background-color 0.2s ease;
84
+ }
85
+
86
+ .crewlet-data-table--default .crewlet-data-table__table tbody tr:hover {
87
+ background: var(--color-surface-elevated);
88
+ }
89
+
90
+ .crewlet-data-table--default .crewlet-data-table__empty {
91
+ color: var(--color-text-secondary);
92
+ font-style: italic;
93
+ text-align: center;
94
+ padding: 40px 20px;
95
+ }
96
+
97
+ /* Resizable columns */
98
+ .crewlet-data-table--default .crewlet-data-table__column-resizer {
99
+ position: absolute;
100
+ right: 0;
101
+ top: 0;
102
+ bottom: 0;
103
+ width: 8px;
104
+ cursor: col-resize;
105
+ user-select: none;
106
+ z-index: 10;
107
+ }
108
+
109
+ .crewlet-data-table--default .crewlet-data-table__column-resizer:hover {
110
+ background: rgba(var(--color-brand-accent-rgb, 84, 105, 212), 0.3);
111
+ }
112
+
113
+ /* Set on <body> while a column is being dragged, so the resize cursor
114
+ and the text-selection lock hold even when the pointer leaves the
115
+ header cell. */
116
+ body.crewlet-data-table-resizing {
117
+ cursor: col-resize;
118
+ user-select: none;
119
+ }
120
+
121
+ /* Sortable headers */
122
+ .crewlet-data-table--default .crewlet-data-table__th--sortable {
123
+ cursor: pointer;
124
+ transition: color 0.2s ease;
125
+ }
126
+
127
+ .crewlet-data-table--default .crewlet-data-table__th--sortable:hover {
128
+ color: var(--color-text-primary);
129
+ }
130
+
131
+ .crewlet-data-table--default .crewlet-data-table__header-content {
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 4px;
135
+ min-width: 0;
136
+ max-width: 100%;
137
+ overflow: hidden;
138
+ padding-right: 0;
139
+ }
140
+
141
+ .crewlet-data-table--default .crewlet-data-table__header-content > span:first-child {
142
+ overflow: hidden;
143
+ text-overflow: ellipsis;
144
+ white-space: nowrap;
145
+ flex: 1;
146
+ min-width: 0;
147
+ }
148
+
149
+ .crewlet-data-table--default .crewlet-data-table__sort-icon {
150
+ font-size: 16px;
151
+ flex-shrink: 0;
152
+ opacity: 0.5;
153
+ transition: opacity 0.2s ease;
154
+ }
155
+
156
+ .crewlet-data-table--default .crewlet-data-table__th--sortable:hover .crewlet-data-table__sort-icon {
157
+ opacity: 0.8;
158
+ }
159
+
160
+ .crewlet-data-table--default .crewlet-data-table__sort-icon.is-active {
161
+ opacity: 1;
162
+ color: var(--color-text-primary);
163
+ }
164
+
165
+ /* Row action button */
166
+ .crewlet-data-table--default .crewlet-data-table__action-btn {
167
+ background: transparent;
168
+ border: none;
169
+ color: var(--color-text-secondary);
170
+ cursor: pointer;
171
+ padding: 4px;
172
+ border-radius: 4px;
173
+ display: inline-flex;
174
+ align-items: center;
175
+ justify-content: center;
176
+ transition: all 0.2s ease;
177
+ }
178
+
179
+ .crewlet-data-table--default .crewlet-data-table__action-btn:hover {
180
+ background: rgba(239, 68, 68, 0.1);
181
+ color: #ef4444;
182
+ }
183
+
184
+ .crewlet-data-table--default .crewlet-data-table__action-btn .material-symbols-outlined {
185
+ font-size: 18px;
186
+ }
187
+
188
+ /* Settings and reset buttons in the Actions column header */
189
+ .crewlet-data-table--default .crewlet-data-table__header-btn {
190
+ position: absolute;
191
+ top: 4px;
192
+ background: transparent;
193
+ border: none;
194
+ color: var(--color-text-secondary);
195
+ cursor: pointer;
196
+ padding: 4px;
197
+ border-radius: 4px;
198
+ display: inline-flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ transition: all 0.2s ease;
202
+ }
203
+
204
+ .crewlet-data-table--default .crewlet-data-table__header-btn:hover {
205
+ background: var(--color-surface-elevated);
206
+ color: var(--color-text-primary);
207
+ }
208
+
209
+ .crewlet-data-table--default .crewlet-data-table__header-btn .material-symbols-outlined {
210
+ font-size: 16px;
211
+ }
212
+
213
+ .crewlet-data-table--default .crewlet-data-table__header-btn--settings {
214
+ right: 4px;
215
+ }
216
+
217
+ .crewlet-data-table--default .crewlet-data-table__header-btn--reset {
218
+ right: 28px;
219
+ }
220
+
221
+ /*
222
+ * The settings modal is uilet's Modal primitive, composed in
223
+ * DataTable.tsx. Its inner controls (the settings sections, the
224
+ * items-per-page chips and the column toggles) have no uilet
225
+ * equivalent and are styled below, namespaced under
226
+ * .crewlet-data-table__ so they never collide with an application's
227
+ * own class names.
228
+ */
229
+
230
+ .crewlet-data-table__settings-section {
231
+ margin-bottom: 24px;
232
+ position: relative;
233
+ }
234
+
235
+ .crewlet-data-table__settings-section:last-child {
236
+ margin-bottom: 0;
237
+ }
238
+
239
+ .crewlet-data-table__settings-section-title {
240
+ font-size: 14px;
241
+ font-weight: 600;
242
+ color: var(--color-text-primary);
243
+ margin: 0 0 12px 0;
244
+ text-transform: uppercase;
245
+ letter-spacing: 0.5px;
246
+ }
247
+
248
+ .crewlet-data-table__settings-section-hint {
249
+ font-size: 13px;
250
+ color: var(--color-text-secondary);
251
+ margin: 0 0 12px 0;
252
+ }
253
+
254
+ /* Items Per Page Options */
255
+ /*
256
+ * Settings modal footer layout. Reset on the left, Cancel + Apply on
257
+ * the right, sharing the modal's footer row via space-between. Modal's
258
+ * default footer is justify-content: flex-end, so the wrapper takes
259
+ * over the row to anchor Reset on the left.
260
+ */
261
+ .crewlet-data-table__settings-footer {
262
+ display: flex;
263
+ justify-content: space-between;
264
+ align-items: center;
265
+ width: 100%;
266
+ gap: 12px;
267
+ flex-wrap: wrap;
268
+ }
269
+
270
+ .crewlet-data-table__settings-footer-right {
271
+ display: flex;
272
+ gap: 8px;
273
+ }
274
+
275
+ /*
276
+ * Reset to Default checkmark confirmation. Renders briefly inside the
277
+ * Button's leadingIcon slot after a click so the operator sees the
278
+ * reset landed; auto-clears after ~1.5s. The colour tracks the
279
+ * theme's success token (with a sensible fallback) so it reads as a
280
+ * positive affordance rather than chrome.
281
+ */
282
+ .crewlet-data-table__reset-confirm-icon {
283
+ font-size: 16px;
284
+ color: var(--color-feedback-success, #22c55e);
285
+ }
286
+
287
+ .crewlet-data-table__items-per-page-options {
288
+ display: flex;
289
+ gap: 8px;
290
+ flex-wrap: wrap;
291
+ }
292
+
293
+ .crewlet-data-table__items-per-page-btn {
294
+ background: var(--color-surface-subtle);
295
+ border: 1px solid var(--color-border-default);
296
+ color: var(--color-text-primary);
297
+ padding: 8px 16px;
298
+ border-radius: 6px;
299
+ font-size: 14px;
300
+ font-weight: 500;
301
+ cursor: pointer;
302
+ transition: all 0.2s ease;
303
+ }
304
+
305
+ .crewlet-data-table__items-per-page-btn:hover {
306
+ background: var(--color-surface-elevated);
307
+ border-color: var(--color-text-secondary);
308
+ }
309
+
310
+ .crewlet-data-table__items-per-page-btn.is-active {
311
+ background: var(--color-brand-accent);
312
+ border-color: var(--color-brand-accent);
313
+ color: #ffffff;
314
+ }
315
+
316
+ /* Column Toggles */
317
+ .crewlet-data-table__column-toggles {
318
+ display: flex;
319
+ flex-direction: column;
320
+ gap: 8px;
321
+ min-height: 350px;
322
+ }
323
+
324
+ .crewlet-data-table__column-toggle-item {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 12px;
328
+ padding: 10px 12px;
329
+ background: var(--color-surface-subtle);
330
+ border: 1px solid var(--color-border-default);
331
+ border-radius: 6px;
332
+ cursor: move;
333
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
334
+ min-height: 42px;
335
+ box-sizing: border-box;
336
+ }
337
+
338
+ .crewlet-data-table__column-toggle-item:hover:not(.is-dragging) {
339
+ background: var(--color-surface-elevated);
340
+ border-color: var(--color-text-secondary);
341
+ }
342
+
343
+ .crewlet-data-table__column-toggles:has(.is-dragging) .crewlet-data-table__column-toggle-item:not(.is-dragging):hover {
344
+ background: var(--color-surface-subtle);
345
+ border-color: var(--color-border-default);
346
+ }
347
+
348
+ .crewlet-data-table__column-toggle-item.is-dragging {
349
+ opacity: 0.5;
350
+ }
351
+
352
+ .crewlet-data-table__column-drag-handle {
353
+ color: var(--color-text-secondary);
354
+ cursor: grab;
355
+ font-size: 20px;
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ .crewlet-data-table__column-drag-handle:active {
360
+ cursor: grabbing;
361
+ }
362
+
363
+ .crewlet-data-table__column-toggle-checkbox {
364
+ width: 18px;
365
+ height: 18px;
366
+ cursor: pointer;
367
+ appearance: none;
368
+ -webkit-appearance: none;
369
+ -moz-appearance: none;
370
+ border: 2px solid var(--color-border-default);
371
+ border-radius: 4px;
372
+ background: var(--color-surface-background);
373
+ position: relative;
374
+ transition: all 0.2s ease;
375
+ flex-shrink: 0;
376
+ }
377
+
378
+ .crewlet-data-table__column-toggle-checkbox:hover {
379
+ border-color: var(--color-brand-accent);
380
+ opacity: 1;
381
+ }
382
+
383
+ .crewlet-data-table__column-toggle-checkbox:checked {
384
+ background: var(--color-brand-accent);
385
+ border-color: var(--color-brand-accent);
386
+ }
387
+
388
+ .crewlet-data-table__column-toggle-checkbox:checked::after {
389
+ content: '';
390
+ position: absolute;
391
+ left: 50%;
392
+ top: 50%;
393
+ width: 4px;
394
+ height: 8px;
395
+ border: solid white;
396
+ border-width: 0 2px 2px 0;
397
+ transform: translate(-50%, -60%) rotate(45deg);
398
+ }
399
+
400
+ .crewlet-data-table__column-toggle-checkbox:focus {
401
+ outline: none;
402
+ box-shadow: var(--shadow-focus, 0 0 0 3px rgba(var(--color-brand-accent-rgb, 84, 105, 212), 0.32));
403
+ }
404
+
405
+ .crewlet-data-table__column-toggle-label {
406
+ font-size: 14px;
407
+ color: var(--color-text-primary);
408
+ user-select: none;
409
+ flex: 1;
410
+ text-align: left;
411
+ }
412
+
413
+ /* Modal footer buttons are uilet <Button> primitives composed in DataTable.tsx. */
414
+
415
+ /*
416
+ * Light theme: the column-toggle checkbox needs higher contrast than
417
+ * --color-border-default on a white surface, so the unchecked state
418
+ * stays visible. The underlying surface / border / text tokens are
419
+ * flipped automatically by @crewlethq/tokens/css/themes.
420
+ */
421
+ body.theme-light .crewlet-data-table__column-toggle-checkbox {
422
+ border-color: rgba(0, 0, 0, 0.4);
423
+ }
424
+
425
+ /*
426
+ * Compact settings modal widgets: the Wrap lines toggle. The page-size
427
+ * control reuses the rich modal's .crewlet-data-table__items-per-page-*
428
+ * chip styles so both modal shapes share one control vocabulary; only
429
+ * the section heading ("Page size" vs "Items per page") differs.
430
+ */
431
+ .crewlet-data-table__toggle-row {
432
+ display: flex;
433
+ align-items: flex-start;
434
+ gap: 10px;
435
+ padding: 6px 4px;
436
+ cursor: pointer;
437
+ }
438
+
439
+ .crewlet-data-table__toggle-checkbox {
440
+ width: 18px;
441
+ height: 18px;
442
+ margin: 0;
443
+ cursor: pointer;
444
+ appearance: none;
445
+ -webkit-appearance: none;
446
+ -moz-appearance: none;
447
+ border: 2px solid var(--color-border-default);
448
+ border-radius: 4px;
449
+ background: var(--color-surface-background);
450
+ position: relative;
451
+ flex-shrink: 0;
452
+ transition: all 0.15s ease;
453
+ margin-top: 1px;
454
+ }
455
+
456
+ .crewlet-data-table__toggle-checkbox:hover {
457
+ border-color: var(--color-brand-accent);
458
+ }
459
+
460
+ .crewlet-data-table__toggle-checkbox:checked {
461
+ background: var(--color-brand-accent);
462
+ border-color: var(--color-brand-accent);
463
+ }
464
+
465
+ .crewlet-data-table__toggle-checkbox:checked::after {
466
+ content: '';
467
+ position: absolute;
468
+ left: 50%;
469
+ top: 50%;
470
+ width: 4px;
471
+ height: 8px;
472
+ border: solid white;
473
+ border-width: 0 2px 2px 0;
474
+ transform: translate(-50%, -60%) rotate(45deg);
475
+ }
476
+
477
+ .crewlet-data-table__toggle-checkbox:focus-visible {
478
+ outline: none;
479
+ box-shadow: var(--shadow-focus, 0 0 0 3px rgba(var(--color-brand-accent-rgb, 84, 105, 212), 0.32));
480
+ }
481
+
482
+ .crewlet-data-table__toggle-text {
483
+ display: flex;
484
+ flex-direction: column;
485
+ gap: 2px;
486
+ user-select: none;
487
+ }
488
+
489
+ .crewlet-data-table__toggle-label {
490
+ font-size: 14px;
491
+ font-weight: 500;
492
+ color: var(--color-text-primary);
493
+ }
494
+
495
+ .crewlet-data-table__toggle-hint {
496
+ font-size: 12px;
497
+ color: var(--color-text-secondary);
498
+ line-height: 1.4;
499
+ }
500
+
501
+ body.theme-light .crewlet-data-table__toggle-checkbox {
502
+ border-color: rgba(0, 0, 0, 0.4);
503
+ }
504
+
505
+ /*
506
+ * Cursor-pagination footer: centered Load more button rendered when
507
+ * the caller passes hasMore + onLoadMore. The terminal state is
508
+ * intentionally silent; an "End of feed" indicator was retired so
509
+ * exhausted tables collapse to nothing rather than a dead label.
510
+ */
511
+ .crewlet-data-table__load-more {
512
+ display: flex;
513
+ align-items: center;
514
+ justify-content: center;
515
+ gap: 8px;
516
+ padding: 8px 0;
517
+ min-height: 32px;
518
+ }
519
+
520
+ /* ─────────────────────────────────────────────────────────────────
521
+ * Compact variant.
522
+ *
523
+ * Lighter table chrome for dense list pages: tight padding, uppercase
524
+ * headers, full-row loading and error states, and the settings cog in
525
+ * the toolbar rather than the header row.
526
+ * Designed to sit inside an external Toolbar + Footer that the host
527
+ * page owns.
528
+ *
529
+ * Tokens declared on the root element are overridable by the host,
530
+ * from a rule that targets the root through the table's className or
531
+ * an ancestor selector (for example `.my-page .crewlet-data-table--compact`),
532
+ * so the active-sort color, the row hover and the archived-row tint
533
+ * can match a non-default palette without uilet needing to know about
534
+ * the host's tokens.
535
+ * ───────────────────────────────────────────────────────────────── */
536
+ .crewlet-data-table--compact {
537
+ --crewlet-data-table-row-hover-bg: var(--color-surface-elevated);
538
+ --crewlet-data-table-active-sort-color: var(--color-brand-accent);
539
+ --crewlet-data-table-archived-tint-rgb: 196, 96, 96;
540
+ width: 100%;
541
+ }
542
+
543
+ /*
544
+ * Horizontal-scroll wrapper around the <table> element only. The
545
+ * header (title + description + cog + pagination), toolbar (filter
546
+ * slot), and Load more footer sit OUTSIDE this scroll viewport so
547
+ * they stay anchored when the table overflows on narrow viewports.
548
+ */
549
+ .crewlet-data-table__rail {
550
+ /*
551
+ * Positioning context for the scrollbar mask. The rail wraps the
552
+ * scroll viewport so the mask can be placed at the rail's bottom-
553
+ * right corner without interfering with the table's natural flow.
554
+ * Width matches the scroll viewport inside.
555
+ */
556
+ position: relative;
557
+ width: 100%;
558
+ }
559
+
560
+ /*
561
+ * Scrollbar mask. When the table holds one or more sticky-right
562
+ * columns, the horizontal scrollbar inside the scroll viewport
563
+ * physically spans the full pane width, including the area visually
564
+ * occupied by the sticky cells. The mask overlays the rightmost
565
+ * portion of that scrollbar with the sticky background so the visible
566
+ * scroll track ends exactly at the left border of the sticky cells.
567
+ * Width is sourced from --crewlet-data-table-sticky-pane-width, which
568
+ * the component sets inline from the live sticky column widths so
569
+ * resize and Reset stay in sync. The default of 0 keeps the mask
570
+ * invisible on tables without a sticky pane.
571
+ *
572
+ * Height is sized to cover the tallest common horizontal scrollbar
573
+ * (Windows / X11 classic) without intruding into the rows above it
574
+ * on macOS overlay scrollbars where the bar is thinner. The mask is
575
+ * pointer-events: none so it never swallows clicks targeted at the
576
+ * underlying cells, even though those clicks already land on the
577
+ * sticky cell that sits above.
578
+ */
579
+ .crewlet-data-table__rail::after {
580
+ content: '';
581
+ position: absolute;
582
+ bottom: 0;
583
+ right: 0;
584
+ width: var(--crewlet-data-table-sticky-pane-width, 0);
585
+ height: 16px;
586
+ background: var(
587
+ --crewlet-data-table-sticky-bg,
588
+ var(--color-surface-elevated, #1f2128)
589
+ );
590
+ pointer-events: none;
591
+ z-index: 2;
592
+ }
593
+
594
+ /*
595
+ * Frozen variant: tables that carry a sticky-right action column.
596
+ * The fit pass guarantees the regular columns redistribute to fit
597
+ * the container at every container-size change, so a horizontal
598
+ * scrollbar is never needed and would only signal a layout that
599
+ * already lost. Locking overflow to hidden also removes the
600
+ * scrollbar mask requirement (no scrollbar means nothing to mask),
601
+ * so the rail drops its ::after pseudo to keep the bottom-right
602
+ * corner clean across surfaces that use a different sticky-bg.
603
+ */
604
+ .crewlet-data-table__rail--frozen .crewlet-data-table__scroll {
605
+ overflow-x: hidden;
606
+ }
607
+
608
+ .crewlet-data-table__rail--frozen::after {
609
+ display: none;
610
+ }
611
+
612
+ .crewlet-data-table__scroll {
613
+ width: 100%;
614
+ overflow-x: auto;
615
+ -webkit-overflow-scrolling: touch;
616
+ }
617
+
618
+ .crewlet-data-table--compact .crewlet-data-table__table {
619
+ /*
620
+ * width: 100% + table-layout: fixed + a trailing spacer column
621
+ * lets the table fill the container at minimum, while still
622
+ * letting the column-resize handle write widths 1:1. The spacer
623
+ * column has no explicit width so it absorbs all the leftover
624
+ * space; user-resized columns stay at their exact widths. When
625
+ * the sum of column widths exceeds the container, the spacer
626
+ * collapses to zero and the horizontal-scroll wrapper handles
627
+ * overflow.
628
+ */
629
+ width: 100%;
630
+ border-collapse: collapse;
631
+ table-layout: fixed;
632
+ }
633
+
634
+ .crewlet-data-table__th--spacer,
635
+ .crewlet-data-table__td--spacer {
636
+ /*
637
+ * Trailing spacer column: invisible, padding-free, takes whatever
638
+ * width is left over after the user-defined columns claim theirs.
639
+ * No padding so it never adds to the natural minimum width; no
640
+ * border on the right so the table's right edge stays clean.
641
+ */
642
+ padding: 0;
643
+ border: 0;
644
+ background: transparent;
645
+ min-width: 0;
646
+ width: auto;
647
+ }
648
+
649
+ /*
650
+ * When the table carries a sticky-right action column, the spacer
651
+ * sits BETWEEN the rightmost regular cell and the pinned action
652
+ * cell. Force a minimum gutter on it so the regular content never
653
+ * touches the action column, even if a state mutation bypasses the
654
+ * fit pass (visibility toggle, controlled column-width override,
655
+ * etc.). The fit pass also reserves this same 16px in its chrome
656
+ * accounting so the gutter and the proportional grow stay in sync.
657
+ */
658
+ .crewlet-data-table--compact
659
+ .crewlet-data-table__table:has(.crewlet-data-table__th--sticky-right)
660
+ .crewlet-data-table__th--spacer,
661
+ .crewlet-data-table--compact
662
+ .crewlet-data-table__table:has(.crewlet-data-table__th--sticky-right)
663
+ .crewlet-data-table__td--spacer {
664
+ min-width: 24px;
665
+ width: 24px;
666
+ }
667
+
668
+ /*
669
+ * Row actions column: pinned to the viewport's right edge with
670
+ * position: sticky so the kebab affordance stays visible during
671
+ * horizontal scroll AND keeps a fixed visual position regardless of
672
+ * how the operator resizes the data columns. The cell intentionally
673
+ * has no background so it blends with the surrounding row tone
674
+ * (default, hover, archived), the parent card surface remains
675
+ * visible underneath. The z-index keeps the kebab above the column
676
+ * separators painted on data cells.
677
+ */
678
+ .crewlet-data-table__th--actions,
679
+ .crewlet-data-table__td--actions {
680
+ position: sticky;
681
+ right: 0;
682
+ z-index: 2;
683
+ background: transparent;
684
+ }
685
+
686
+ /*
687
+ * Opt-in sticky-right modifier for caller-defined columns. Pins the
688
+ * column to the right edge of the scroll container so a trailing
689
+ * action (a Details link, Edit chip, etc.) stays visible regardless
690
+ * of horizontal scroll or how the operator resizes earlier data
691
+ * columns. The cell paints an opaque background sourced from a
692
+ * theme-overridable token so content scrolling behind the cell does
693
+ * not bleed through the link text. The header z-index sits a layer
694
+ * above body cells so the column boundary remains crisp at the
695
+ * sticky edge. Callers tinting the surface re-bind the token via a
696
+ * wrapper rule (a card-style className on the table, for example).
697
+ */
698
+ .crewlet-data-table__th--sticky-right,
699
+ .crewlet-data-table__td--sticky-right {
700
+ position: sticky;
701
+ right: 0;
702
+ z-index: 2;
703
+ background: var(--crewlet-data-table-sticky-bg, var(--color-surface-elevated, #1f2128));
704
+ }
705
+
706
+ /*
707
+ * Sticky cells are the row's trailing action area; the affordance
708
+ * they hold sits centered inside the sticky pane so the chip reads
709
+ * as the focal point of the pane rather than tucked against either
710
+ * edge. The base compact cell rule pins text-align: left at higher
711
+ * specificity, so the override is written with the full selector
712
+ * chain to win the cascade. Inline-flex content (the typical action
713
+ * chip pattern) respects the cell's text-align so this is sufficient
714
+ * on its own, no display change to the table-cell needed.
715
+ */
716
+ .crewlet-data-table--compact .crewlet-data-table__table th.crewlet-data-table__th--sticky-right,
717
+ .crewlet-data-table--compact .crewlet-data-table__table td.crewlet-data-table__td--sticky-right {
718
+ text-align: center;
719
+ }
720
+
721
+ .crewlet-data-table__th--sticky-right {
722
+ z-index: 3;
723
+ background: var(
724
+ --crewlet-data-table-sticky-header-bg,
725
+ var(--crewlet-data-table-sticky-bg, var(--color-surface-elevated, #1f2128))
726
+ );
727
+ }
728
+
729
+ /*
730
+ * Row hover + expanded states change the <tr> background, but cells
731
+ * with their own background (the sticky-right column needs one so
732
+ * scrolled content does not bleed through) do not pick that change
733
+ * up automatically. Mirror the row-state colors onto the sticky
734
+ * cell so the right edge stays visually attached to the row it
735
+ * belongs to instead of standing out as a different shade.
736
+ */
737
+ .crewlet-data-table--compact tbody tr:hover .crewlet-data-table__td--sticky-right,
738
+ .crewlet-data-table--compact .crewlet-data-table__row--expandable.is-expanded .crewlet-data-table__td--sticky-right {
739
+ background: var(--crewlet-data-table-row-hover-bg);
740
+ }
741
+
742
+ /*
743
+ * Unstyled button-as-link cells (an agent name that navigates, a
744
+ * schedule that opens a drawer) must read as table text. Browsers
745
+ * center button text by their UA stylesheet, so a cell value that
746
+ * wraps onto multiple lines would center-align its lines instead of
747
+ * following the cell's alignment. Inherit pins buttons to whatever
748
+ * the cell declares; components that center via flex (Button,
749
+ * IconButton) are unaffected because their alignment comes from
750
+ * justify-content, not text-align.
751
+ */
752
+ .crewlet-data-table__table td button {
753
+ text-align: inherit;
754
+ }
755
+
756
+ .crewlet-data-table--compact .crewlet-data-table__table th,
757
+ .crewlet-data-table--compact .crewlet-data-table__table td {
758
+ padding: 10px 12px;
759
+ text-align: left;
760
+ font-size: 13px;
761
+ border-bottom: 1px solid var(--color-border-default);
762
+ /*
763
+ * Center cells against the row's tallest cell. Tables like an
764
+ * activity feed mix short cells ("4h ago", "success") with tall
765
+ * identity blocks (avatar + name + id stacked vertically),
766
+ * so middle alignment keeps the short cells from sticking to
767
+ * the top while the multi-line ones sit at the row's full height.
768
+ */
769
+ vertical-align: middle;
770
+ /*
771
+ * Default to truncate-with-ellipsis (AWS-style data tables). The
772
+ * Wrap lines toggle in the compact settings modal flips this on
773
+ * by adding .crewlet-data-table--wrap-lines on the root, which
774
+ * the override below re-enables wrapping for. Long unbroken
775
+ * tokens (UUIDs) get clipped with an ellipsis instead of pushing
776
+ * the column wider. (max-width is intentionally NOT set here:
777
+ * table-layout: fixed already takes the th's width as the column
778
+ * width, and forcing max-width: 0 collapses the column to zero
779
+ * under some browser implementations.)
780
+ */
781
+ white-space: nowrap;
782
+ overflow: hidden;
783
+ text-overflow: ellipsis;
784
+ }
785
+
786
+ /*
787
+ * Wrap-lines toggle only affects BODY cells. Headers always keep
788
+ * their content on a single line; a narrow column truncates the
789
+ * label with an ellipsis instead of wrapping onto two lines, which
790
+ * would change row heights every time someone resizes a column.
791
+ */
792
+ .crewlet-data-table--compact.crewlet-data-table--wrap-lines .crewlet-data-table__table td {
793
+ white-space: normal;
794
+ overflow: visible;
795
+ overflow-wrap: anywhere;
796
+ word-break: break-word;
797
+ max-width: none;
798
+ }
799
+
800
+ /*
801
+ * Force-pin the header cells (and their inner sort-button label) to
802
+ * single-line truncate, even when --wrap-lines is on. The sort
803
+ * button is a flex row [label | arrow]; the label takes whatever
804
+ * space is left, then ellipsises.
805
+ */
806
+ .crewlet-data-table--compact .crewlet-data-table__table th,
807
+ .crewlet-data-table--compact.crewlet-data-table--wrap-lines .crewlet-data-table__table th {
808
+ white-space: nowrap;
809
+ overflow: hidden;
810
+ text-overflow: ellipsis;
811
+ }
812
+
813
+ .crewlet-data-table--compact .crewlet-data-table__sort-button {
814
+ overflow: hidden;
815
+ }
816
+
817
+ .crewlet-data-table--compact .crewlet-data-table__sort-button > span:first-child {
818
+ white-space: nowrap;
819
+ text-overflow: ellipsis;
820
+ }
821
+
822
+ .crewlet-data-table--compact .crewlet-data-table__table th {
823
+ color: var(--color-text-tertiary);
824
+ font-weight: 500;
825
+ letter-spacing: 0.03em;
826
+ text-transform: uppercase;
827
+ font-size: 11px;
828
+ /* Headers must read as a single tight line; never wrap onto two lines
829
+ because the next column is unusually narrow. */
830
+ white-space: nowrap;
831
+ }
832
+
833
+ .crewlet-data-table--compact .crewlet-data-table__table tbody tr:hover {
834
+ background: var(--crewlet-data-table-row-hover-bg);
835
+ }
836
+
837
+ /*
838
+ * Thin vertical separator between column headers. Painted as an
839
+ * absolutely positioned pseudo-element on the right edge of every
840
+ * thead th, with three exclusions:
841
+ * 1. The literal last th (no trailing separator at the row's edge).
842
+ * 2. Chrome columns themselves (the --chrome modifier on the
843
+ * empty actions / expand-chevron headers) skip the separator
844
+ * regardless of position. This matters when the expand chevron
845
+ * sits at the left edge.
846
+ * 3. A th whose next sibling is a chrome column also skips the
847
+ * separator, so the last data column never has a line dividing
848
+ * it from a trailing chrome column.
849
+ * The line is inset 6px from top and bottom so it reads as a column
850
+ * delimiter rather than a horizontal border. Only the header row
851
+ * carries separators; data rows stay clean.
852
+ */
853
+ .crewlet-data-table--compact .crewlet-data-table__table thead th {
854
+ position: relative;
855
+ }
856
+
857
+ .crewlet-data-table--compact .crewlet-data-table__table thead th:not(:last-child):not(.crewlet-data-table__th--chrome):not(:has(+ .crewlet-data-table__th--chrome))::after {
858
+ content: '';
859
+ position: absolute;
860
+ top: 6px;
861
+ bottom: 6px;
862
+ right: 0;
863
+ width: 1px;
864
+ background: var(--color-border-default);
865
+ pointer-events: none;
866
+ }
867
+
868
+ .crewlet-data-table--compact .crewlet-data-table__row--clickable {
869
+ cursor: pointer;
870
+ }
871
+
872
+ /*
873
+ * Pagination + settings controls live in the toolbar-end zone (see
874
+ * the toolbar rules higher up); these rules style the buttons
875
+ * themselves.
876
+ */
877
+ .crewlet-data-table__pagination {
878
+ display: inline-flex;
879
+ align-items: center;
880
+ gap: 2px;
881
+ }
882
+
883
+ /*
884
+ * Thin vertical separator between the pagination chevrons and the
885
+ * settings cog. Painted as a pseudo-element on the left edge of the
886
+ * settings button (inset 4px from top/bottom so it reads as a
887
+ * delimiter, not a border). Only renders when the settings button
888
+ * sits adjacent to the pagination block.
889
+ */
890
+ .crewlet-data-table__pagination + .crewlet-data-table__settings-btn {
891
+ position: relative;
892
+ /*
893
+ * Widen the gap between the pagination cluster and the settings
894
+ * cog so the separator can sit close to the pagination side with
895
+ * comfortable breathing room before the cog.
896
+ */
897
+ margin-left: 14px;
898
+ }
899
+
900
+ .crewlet-data-table__pagination + .crewlet-data-table__settings-btn::before {
901
+ content: '';
902
+ position: absolute;
903
+ top: 4px;
904
+ bottom: 4px;
905
+ /*
906
+ * Anchor the line ~2px past the pagination block's right edge
907
+ * (12px from the settings button's left edge) so it reads as the
908
+ * closer of the pagination cluster, not a divider centered in
909
+ * the gap.
910
+ */
911
+ left: -12px;
912
+ width: 1px;
913
+ background: var(--color-border-default);
914
+ }
915
+
916
+ .crewlet-data-table__page-btn,
917
+ .crewlet-data-table__settings-btn {
918
+ display: inline-flex;
919
+ align-items: center;
920
+ justify-content: center;
921
+ width: 28px;
922
+ height: 28px;
923
+ padding: 0;
924
+ background: transparent;
925
+ border: 1px solid transparent;
926
+ border-radius: 6px;
927
+ color: var(--color-text-secondary);
928
+ cursor: pointer;
929
+ transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
930
+ }
931
+
932
+ .crewlet-data-table__page-btn:hover:not(:disabled),
933
+ .crewlet-data-table__settings-btn:hover {
934
+ background: var(--color-surface-elevated);
935
+ border-color: var(--color-border-default);
936
+ color: var(--color-text-primary);
937
+ }
938
+
939
+ .crewlet-data-table__page-btn:disabled {
940
+ opacity: 0.35;
941
+ cursor: default;
942
+ }
943
+
944
+ .crewlet-data-table__page-btn .material-symbols-outlined,
945
+ .crewlet-data-table__settings-btn .material-symbols-outlined {
946
+ font-size: 18px;
947
+ }
948
+
949
+ .crewlet-data-table__page-number {
950
+ display: inline-flex;
951
+ align-items: center;
952
+ justify-content: center;
953
+ min-width: 22px;
954
+ padding: 0 6px;
955
+ font-size: 12px;
956
+ font-variant-numeric: tabular-nums;
957
+ color: var(--color-text-primary);
958
+ }
959
+
960
+ /* ─── Toolbar slot (filter bars, search, batch actions) ────────── */
961
+ /*
962
+ * Two-zone toolbar: the caller's content (filter pills, search) sits
963
+ * on the LEFT (toolbar-start); the table's own controls (pagination
964
+ * chevrons + settings cog) sit on the RIGHT (toolbar-end). Either
965
+ * zone can be absent; the toolbar only renders when at least one
966
+ * has content. Toolbar-end always aligns to the right edge so the
967
+ * cog and chevrons hold a consistent position across pages.
968
+ */
969
+ /*
970
+ * Optional header block. Sits above the toolbar and renders the
971
+ * table's title (section name) and description (one-line context)
972
+ * when callers pass either prop. Folds what used to be a per-page
973
+ * <section><header><h2/><p/></header><DataTable/></section> pattern
974
+ * into the primitive so the entire surface shares one card.
975
+ */
976
+ /*
977
+ * Header row layout. Icon (optional) + title/description stack on the
978
+ * left, right-side controls (pagination + settings cog) on the right.
979
+ * Wraps on narrow viewports so the controls drop below the text rather
980
+ * than colliding with a long description.
981
+ */
982
+ .crewlet-data-table__header {
983
+ display: flex;
984
+ align-items: flex-start;
985
+ gap: 12px;
986
+ flex-wrap: wrap;
987
+ padding: 14px 16px 12px;
988
+ border-bottom: 1px solid var(--color-border-default);
989
+ background: var(--color-surface-subtle);
990
+ }
991
+
992
+ .crewlet-data-table__header-icon {
993
+ font-size: 22px;
994
+ color: var(--color-text-secondary);
995
+ flex-shrink: 0;
996
+ margin-top: 1px;
997
+ }
998
+
999
+ .crewlet-data-table__header-text {
1000
+ display: flex;
1001
+ flex-direction: column;
1002
+ gap: 4px;
1003
+ flex: 1;
1004
+ min-width: 0;
1005
+ }
1006
+
1007
+ .crewlet-data-table__header .crewlet-data-table__toolbar-end {
1008
+ flex-shrink: 0;
1009
+ margin-left: auto;
1010
+ }
1011
+
1012
+ .crewlet-data-table__title {
1013
+ margin: 0;
1014
+ font-size: 16px;
1015
+ font-weight: 600;
1016
+ color: var(--color-text-primary);
1017
+ letter-spacing: -0.005em;
1018
+ }
1019
+
1020
+ .crewlet-data-table__description {
1021
+ margin: 0;
1022
+ font-size: 13px;
1023
+ color: var(--color-text-secondary);
1024
+ line-height: 1.5;
1025
+ }
1026
+
1027
+ /*
1028
+ * When the header and the toolbar are both present, the toolbar's
1029
+ * top edge already meets the header's bottom border, so the toolbar
1030
+ * keeps its own bottom border (table head delimiter) without painting
1031
+ * a second top border.
1032
+ */
1033
+ .crewlet-data-table__toolbar {
1034
+ display: flex;
1035
+ align-items: flex-end;
1036
+ justify-content: space-between;
1037
+ gap: 16px;
1038
+ flex-wrap: wrap;
1039
+ padding: 12px 14px;
1040
+ border-bottom: 1px solid var(--color-border-default);
1041
+ background: var(--color-surface-subtle);
1042
+ }
1043
+
1044
+ .crewlet-data-table__toolbar-start {
1045
+ display: flex;
1046
+ align-items: flex-end;
1047
+ flex-wrap: wrap;
1048
+ gap: 12px;
1049
+ flex: 1 1 auto;
1050
+ min-width: 0;
1051
+ }
1052
+
1053
+ /*
1054
+ * Loading skeleton. Shimmer bars painted inside the real table body
1055
+ * while `loading` is true and no rows exist. The pulse mirrors the
1056
+ * Skeleton primitive's timing so mixed pages (skeleton cards above a
1057
+ * loading table) breathe in sync.
1058
+ */
1059
+ @keyframes crewlet-data-table-skeleton-pulse {
1060
+ 0%, 100% { opacity: 1; }
1061
+ 50% { opacity: 0.45; }
1062
+ }
1063
+
1064
+ .crewlet-data-table__skeleton-bar {
1065
+ display: inline-block;
1066
+ height: 12px;
1067
+ border-radius: 4px;
1068
+ background: var(--color-surface-elevated, var(--color-surface-muted));
1069
+ animation: crewlet-data-table-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1070
+ vertical-align: middle;
1071
+ }
1072
+
1073
+ /*
1074
+ * Clear-filters link. Sits at the end of the filter slot, baseline-
1075
+ * aligned with the filter controls (the slot uses align-items:
1076
+ * flex-end, so the bottom padding tucks the text to the same line
1077
+ * as the Select triggers). Rendered only while a filter is active.
1078
+ */
1079
+ .crewlet-data-table__clear-filters {
1080
+ background: none;
1081
+ border: none;
1082
+ padding: 0 0 7px;
1083
+ font-size: 12.5px;
1084
+ font-family: inherit;
1085
+ color: var(--color-brand-accent);
1086
+ cursor: pointer;
1087
+ white-space: nowrap;
1088
+ align-self: flex-end;
1089
+ /* Pin to the right edge of the toolbar row (before the pagination
1090
+ cluster when present) so the affordance sits in the same place
1091
+ on every table, regardless of the filter slot's own layout. */
1092
+ margin-left: auto;
1093
+ flex: 0 0 auto;
1094
+ }
1095
+
1096
+ .crewlet-data-table__clear-filters:hover {
1097
+ text-decoration: underline;
1098
+ }
1099
+
1100
+ /*
1101
+ * Pagination + settings cog. Always pinned to the right edge of the
1102
+ * toolbar row, even when:
1103
+ * - the toolbar has no filter slot (solo right-side controls)
1104
+ * - the row wraps onto two lines on narrow viewports
1105
+ * `margin-left: auto` consumes the leftover row space, pushing the
1106
+ * cluster past the filter slot (or to the row's right edge when it
1107
+ * is alone). justify-content: space-between alone is not enough,
1108
+ * since space-between with one item left-aligns it.
1109
+ */
1110
+ .crewlet-data-table__toolbar-end {
1111
+ display: flex;
1112
+ align-items: center;
1113
+ gap: 8px;
1114
+ flex-shrink: 0;
1115
+ margin-left: auto;
1116
+ }
1117
+
1118
+ /* ─── Expandable rows ──────────────────────────────────────────── */
1119
+
1120
+ /*
1121
+ * Chevron column: fixed 32px width, zero horizontal padding on both
1122
+ * the header and the body cells. With the same padding on both, the
1123
+ * `>` glyph in every data row lines up vertically beneath the
1124
+ * expand-all `>` in the header row.
1125
+ */
1126
+ .crewlet-data-table__expand-header,
1127
+ .crewlet-data-table--compact .crewlet-data-table__table th.crewlet-data-table__expand-header {
1128
+ width: 32px;
1129
+ text-align: center;
1130
+ padding-left: 0;
1131
+ padding-right: 0;
1132
+ }
1133
+
1134
+ .crewlet-data-table--compact .crewlet-data-table__table td.crewlet-data-table__expand-cell {
1135
+ width: 32px;
1136
+ text-align: center;
1137
+ color: var(--color-text-tertiary);
1138
+ padding-left: 0;
1139
+ padding-right: 0;
1140
+ }
1141
+
1142
+ /*
1143
+ * Expand-all header button. Sits inside the chevron column header
1144
+ * cell, paints the right-pointing chevron, rotates to point down when
1145
+ * every visible row is expanded. Clicking toggles between expand-all
1146
+ * and collapse-all.
1147
+ */
1148
+ .crewlet-data-table__expand-all-btn {
1149
+ /*
1150
+ * Tight 22x22 hit target around the chevron glyph so the hover
1151
+ * highlight reads as a small square button rather than filling the
1152
+ * whole header cell. The parent header centers it horizontally.
1153
+ */
1154
+ width: 22px;
1155
+ height: 22px;
1156
+ display: inline-flex;
1157
+ align-items: center;
1158
+ justify-content: center;
1159
+ background: transparent;
1160
+ border: none;
1161
+ padding: 0;
1162
+ cursor: pointer;
1163
+ color: var(--color-text-tertiary);
1164
+ border-radius: 4px;
1165
+ transition: background-color 0.15s ease, color 0.15s ease;
1166
+ }
1167
+
1168
+ .crewlet-data-table__expand-all-btn:hover {
1169
+ background: var(--color-surface-elevated);
1170
+ color: var(--color-text-primary);
1171
+ }
1172
+
1173
+ .crewlet-data-table__expand-all-btn:focus-visible {
1174
+ outline: none;
1175
+ box-shadow: var(--shadow-focus, 0 0 0 3px rgba(var(--color-brand-accent-rgb, 84, 105, 212), 0.32));
1176
+ }
1177
+
1178
+ .crewlet-data-table__chevron {
1179
+ font-size: 18px;
1180
+ line-height: 1;
1181
+ transition: transform 0.18s ease;
1182
+ display: inline-block;
1183
+ }
1184
+
1185
+ /*
1186
+ * `chevron_right` glyph points right by default; rotating 90deg
1187
+ * makes it point down to signal "expanded". Both the per-row
1188
+ * chevron and the expand-all header chevron share this transition.
1189
+ */
1190
+ .crewlet-data-table__chevron.is-open {
1191
+ transform: rotate(90deg);
1192
+ }
1193
+
1194
+ .crewlet-data-table--compact .crewlet-data-table__row--expandable.is-expanded {
1195
+ background: var(--crewlet-data-table-row-hover-bg);
1196
+ }
1197
+
1198
+ .crewlet-data-table__expanded-row td.crewlet-data-table__expanded-cell {
1199
+ padding: 16px 14px;
1200
+ background: var(--color-surface-subtle);
1201
+ border-bottom: 1px solid var(--color-border-default);
1202
+ }
1203
+
1204
+ /* In comfortable density, the expanded panel inherits the same outer
1205
+ 24px margin as its parent row so the detail content lines up with
1206
+ the row's first and last columns. */
1207
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__expanded-row td.crewlet-data-table__expanded-cell {
1208
+ padding: 16px 24px;
1209
+ }
1210
+
1211
+ /*
1212
+ * Density="comfortable" overrides. Used by surfaces that live inside a
1213
+ * section card with 24px inner padding, where the
1214
+ * canonical compact density (13px font, 10/12px cell padding) looks
1215
+ * cramped against the surrounding card chrome. The font and cell
1216
+ * padding bump up; the first / last column gain extra outer padding
1217
+ * so the row aligns with the card's left and right edges.
1218
+ */
1219
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table {
1220
+ font-size: 14px;
1221
+ }
1222
+
1223
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table th,
1224
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table td {
1225
+ padding: 14px 14px;
1226
+ }
1227
+
1228
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table th {
1229
+ padding: 12px 14px;
1230
+ background: var(--color-surface-subtle);
1231
+ }
1232
+
1233
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table th:first-child,
1234
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table td:first-child {
1235
+ padding-left: 24px;
1236
+ }
1237
+
1238
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table th:last-child,
1239
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__table td:last-child {
1240
+ padding-right: 24px;
1241
+ }
1242
+
1243
+ /*
1244
+ * Sortable-header button inside the comfortable density needs the same
1245
+ * horizontal padding bumps so the label sits flush with adjacent cells
1246
+ * (the button supplants the th's padding when sortable=true).
1247
+ */
1248
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__sort-button {
1249
+ padding: 12px 14px;
1250
+ }
1251
+
1252
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__th--sortable:first-child .crewlet-data-table__sort-button {
1253
+ padding-left: 24px;
1254
+ }
1255
+
1256
+ .crewlet-data-table--compact.crewlet-data-table--density-comfortable .crewlet-data-table__th--sortable:last-child .crewlet-data-table__sort-button {
1257
+ padding-right: 24px;
1258
+ }
1259
+
1260
+ /* Sortable header. The button hosts the click target so the arrow icon
1261
+ is part of the same hit region as the label. */
1262
+ .crewlet-data-table--compact .crewlet-data-table__th--sortable {
1263
+ padding: 0 !important;
1264
+ }
1265
+
1266
+ .crewlet-data-table--compact .crewlet-data-table__sort-button {
1267
+ display: inline-flex;
1268
+ align-items: center;
1269
+ /*
1270
+ * Label on the left, sort arrow pinned to the right edge of the
1271
+ * column. justify-content: space-between with gap stretches the
1272
+ * row so the arrow always sits flush against the column boundary
1273
+ * regardless of label length. The flex behaviour also makes the
1274
+ * label and arrow share one hit region.
1275
+ */
1276
+ justify-content: space-between;
1277
+ gap: 8px;
1278
+ width: 100%;
1279
+ padding: 10px 12px;
1280
+ background: transparent;
1281
+ border: 0;
1282
+ cursor: pointer;
1283
+ color: inherit;
1284
+ font: inherit;
1285
+ text-transform: inherit;
1286
+ letter-spacing: inherit;
1287
+ font-weight: inherit;
1288
+ text-align: left;
1289
+ }
1290
+
1291
+ .crewlet-data-table--compact .crewlet-data-table__sort-button > span:first-child {
1292
+ min-width: 0;
1293
+ overflow: hidden;
1294
+ text-overflow: ellipsis;
1295
+ }
1296
+
1297
+ .crewlet-data-table--compact .crewlet-data-table__sort-button .material-symbols-outlined {
1298
+ flex-shrink: 0;
1299
+ margin-left: auto;
1300
+ }
1301
+
1302
+ .crewlet-data-table--compact .crewlet-data-table__sort-button:hover {
1303
+ color: var(--color-text-secondary);
1304
+ }
1305
+
1306
+ .crewlet-data-table--compact .crewlet-data-table__sort-button .material-symbols-outlined {
1307
+ font-size: 14px;
1308
+ opacity: 0.45;
1309
+ }
1310
+
1311
+ .crewlet-data-table--compact .crewlet-data-table__th--sortable.is-active .crewlet-data-table__sort-button {
1312
+ color: var(--crewlet-data-table-active-sort-color);
1313
+ }
1314
+
1315
+ .crewlet-data-table--compact .crewlet-data-table__th--sortable.is-active .crewlet-data-table__sort-button .material-symbols-outlined {
1316
+ opacity: 1;
1317
+ }
1318
+
1319
+ /*
1320
+ * Compact column resizer. A wide hit area straddling the column's
1321
+ * right edge: 8px total, centered on the boundary (4px into the
1322
+ * cell, 4px past). Hovering reveals a soft accent strip so the
1323
+ * operator can find the handle without it cluttering the default
1324
+ * header. mousedown wiring is owned by the JSX; this rule just
1325
+ * paints the affordance and sets the resize cursor.
1326
+ */
1327
+ .crewlet-data-table__resizer {
1328
+ position: absolute;
1329
+ top: 0;
1330
+ bottom: 0;
1331
+ /*
1332
+ * Wide hit area straddling the column boundary: 16px total, 8px
1333
+ * on each side. Gives the operator a generous grab zone so the
1334
+ * drag doesn't drop out when the mouse drifts a few pixels off
1335
+ * the visible separator line.
1336
+ */
1337
+ right: -8px;
1338
+ width: 16px;
1339
+ cursor: col-resize;
1340
+ user-select: none;
1341
+ z-index: 2;
1342
+ }
1343
+
1344
+ .crewlet-data-table__resizer::before {
1345
+ content: '';
1346
+ position: absolute;
1347
+ top: 4px;
1348
+ bottom: 4px;
1349
+ left: 50%;
1350
+ width: 2px;
1351
+ transform: translateX(-50%);
1352
+ background: transparent;
1353
+ border-radius: 1px;
1354
+ transition: background-color 0.15s ease;
1355
+ }
1356
+
1357
+ .crewlet-data-table__resizer:hover::before,
1358
+ .crewlet-data-table__resizer:active::before {
1359
+ background: var(--color-brand-accent);
1360
+ }
1361
+
1362
+ .crewlet-data-table--compact .crewlet-data-table__empty {
1363
+ padding: 24px;
1364
+ text-align: center;
1365
+ color: var(--color-text-tertiary);
1366
+ font-size: 13px;
1367
+ }
1368
+
1369
+ /* Archived row treatment: subdued tone plus a left edge accent in the
1370
+ tint colour so the row reads as "still here for context but not
1371
+ active". A host flips --crewlet-data-table-archived-tint-rgb to pick
1372
+ its own palette (for example a muted warning red). */
1373
+ .crewlet-data-table--compact .crewlet-data-table__table tbody tr.crewlet-data-table__row--archived td {
1374
+ opacity: 0.55;
1375
+ background: linear-gradient(
1376
+ 90deg,
1377
+ rgba(var(--crewlet-data-table-archived-tint-rgb), 0.05),
1378
+ transparent 60%
1379
+ );
1380
+ }
1381
+
1382
+ .crewlet-data-table--compact .crewlet-data-table__table tbody tr.crewlet-data-table__row--archived td:first-child {
1383
+ position: relative;
1384
+ }
1385
+
1386
+ .crewlet-data-table--compact .crewlet-data-table__table tbody tr.crewlet-data-table__row--archived td:first-child::before {
1387
+ content: '';
1388
+ position: absolute;
1389
+ top: 0;
1390
+ bottom: 0;
1391
+ left: 0;
1392
+ width: 2px;
1393
+ background: rgba(var(--crewlet-data-table-archived-tint-rgb), 0.5);
1394
+ }
1395
+
1396
+ .crewlet-data-table--compact .crewlet-data-table__table tbody tr.crewlet-data-table__row--archived:hover td {
1397
+ opacity: 0.75;
1398
+ }
1399
+
1400
+ /* ─── Copyable cell affordance ─────────────────────────────────── */
1401
+ .crewlet-data-table__copy-cell {
1402
+ display: inline-flex;
1403
+ align-items: center;
1404
+ gap: 6px;
1405
+ max-width: 100%;
1406
+ }
1407
+
1408
+ .crewlet-data-table__copy-cell-value {
1409
+ min-width: 0;
1410
+ overflow: hidden;
1411
+ text-overflow: ellipsis;
1412
+ white-space: nowrap;
1413
+ }
1414
+
1415
+ .crewlet-data-table__copy-cell-button {
1416
+ flex-shrink: 0;
1417
+ background: transparent;
1418
+ border: 1px solid transparent;
1419
+ border-radius: 4px;
1420
+ width: 20px;
1421
+ height: 20px;
1422
+ padding: 0;
1423
+ display: inline-flex;
1424
+ align-items: center;
1425
+ justify-content: center;
1426
+ color: var(--color-text-tertiary);
1427
+ cursor: pointer;
1428
+ opacity: 0;
1429
+ transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
1430
+ }
1431
+
1432
+ .crewlet-data-table__copy-cell-button .material-symbols-outlined {
1433
+ font-size: 14px;
1434
+ line-height: 1;
1435
+ }
1436
+
1437
+ /* Reveal the copy button when the parent row is hovered (compact tables
1438
+ only) or when the cell itself is hovered / focus-visible. The cell
1439
+ selector keeps the affordance reachable outside a table context too. */
1440
+ .crewlet-data-table--compact tbody tr:hover .crewlet-data-table__copy-cell-button,
1441
+ .crewlet-data-table__copy-cell-button:focus-visible,
1442
+ .crewlet-data-table__copy-cell:hover .crewlet-data-table__copy-cell-button {
1443
+ opacity: 1;
1444
+ }
1445
+
1446
+ .crewlet-data-table__copy-cell-button:hover {
1447
+ background: rgba(var(--color-brand-accent-rgb, 84, 105, 212), 0.10);
1448
+ border-color: rgba(var(--color-brand-accent-rgb, 84, 105, 212), 0.32);
1449
+ color: var(--color-brand-accent);
1450
+ }
1451
+
1452
+ .crewlet-data-table__copy-cell-button.is-copied {
1453
+ color: var(--color-brand-accent);
1454
+ opacity: 1;
1455
+ }
1456
+
1457
+ /* ─── Row actions kebab menu ──────────────────────────────────── */
1458
+ .crewlet-data-table__row-actions-trigger {
1459
+ display: inline-flex;
1460
+ align-items: center;
1461
+ justify-content: center;
1462
+ width: 28px;
1463
+ height: 28px;
1464
+ padding: 0;
1465
+ background: transparent;
1466
+ border: 1px solid transparent;
1467
+ border-radius: 6px;
1468
+ color: var(--color-text-tertiary);
1469
+ cursor: pointer;
1470
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
1471
+ }
1472
+
1473
+ .crewlet-data-table__row-actions-trigger:hover,
1474
+ .crewlet-data-table__row-actions-trigger.is-open {
1475
+ background: var(--color-surface-elevated);
1476
+ border-color: var(--color-border-default);
1477
+ color: var(--color-text-primary);
1478
+ }
1479
+
1480
+ .crewlet-data-table__row-actions-trigger:focus-visible {
1481
+ outline: none;
1482
+ border-color: var(--crewlet-data-table-active-sort-color, var(--color-brand-accent));
1483
+ box-shadow: 0 0 0 3px rgba(var(--color-brand-accent-rgb, 84, 105, 212), 0.18);
1484
+ }
1485
+
1486
+ .crewlet-data-table__row-actions-trigger .material-symbols-outlined {
1487
+ font-size: 18px;
1488
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
1489
+ }
1490
+
1491
+ .crewlet-data-table__row-actions-popover {
1492
+ min-width: 220px;
1493
+ padding: 4px;
1494
+ background: var(--color-surface-subtle);
1495
+ border: 1px solid var(--color-border-default);
1496
+ border-radius: 10px;
1497
+ box-shadow: var(--shadow-lg, 0 12px 28px rgba(0, 0, 0, 0.45));
1498
+ }
1499
+
1500
+ .crewlet-data-table__row-actions-list {
1501
+ list-style: none;
1502
+ margin: 0;
1503
+ padding: 0;
1504
+ display: flex;
1505
+ flex-direction: column;
1506
+ }
1507
+
1508
+ .crewlet-data-table__row-actions-divider {
1509
+ height: 1px;
1510
+ margin: 4px 6px;
1511
+ background: var(--color-border-default);
1512
+ }
1513
+
1514
+ .crewlet-data-table__row-actions-item {
1515
+ display: flex;
1516
+ align-items: flex-start;
1517
+ gap: 10px;
1518
+ width: 100%;
1519
+ padding: 8px 10px;
1520
+ background: transparent;
1521
+ border: 0;
1522
+ border-radius: 6px;
1523
+ color: var(--color-text-primary);
1524
+ font: inherit;
1525
+ font-size: 13px;
1526
+ text-align: left;
1527
+ cursor: pointer;
1528
+ transition: background 0.12s ease, color 0.12s ease;
1529
+ }
1530
+
1531
+ .crewlet-data-table__row-actions-item:hover:not(.is-disabled) {
1532
+ background: var(--color-surface-elevated);
1533
+ }
1534
+
1535
+ .crewlet-data-table__row-actions-item.is-disabled {
1536
+ cursor: not-allowed;
1537
+ color: var(--color-text-tertiary);
1538
+ opacity: 0.55;
1539
+ }
1540
+
1541
+ .crewlet-data-table__row-actions-item-icon {
1542
+ flex-shrink: 0;
1543
+ font-size: 18px;
1544
+ line-height: 1.1;
1545
+ color: var(--color-text-secondary);
1546
+ font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
1547
+ }
1548
+
1549
+ .crewlet-data-table__row-actions-item:hover:not(.is-disabled) .crewlet-data-table__row-actions-item-icon {
1550
+ color: var(--color-text-primary);
1551
+ }
1552
+
1553
+ .crewlet-data-table__row-actions-item-text {
1554
+ display: flex;
1555
+ flex-direction: column;
1556
+ min-width: 0;
1557
+ flex: 1;
1558
+ }
1559
+
1560
+ .crewlet-data-table__row-actions-item-label {
1561
+ font-weight: 500;
1562
+ }
1563
+
1564
+ .crewlet-data-table__row-actions-item-description {
1565
+ font-size: 11.5px;
1566
+ color: var(--color-text-tertiary);
1567
+ margin-top: 2px;
1568
+ }
1569
+
1570
+ /* Primary action: tinted with the active-sort accent so it reads as the
1571
+ headline action. A host overrides the custom property to substitute
1572
+ its own accent. */
1573
+ .crewlet-data-table__row-actions-item--primary {
1574
+ color: var(--crewlet-data-table-active-sort-color, var(--color-brand-accent));
1575
+ }
1576
+
1577
+ .crewlet-data-table__row-actions-item--primary .crewlet-data-table__row-actions-item-icon {
1578
+ color: var(--crewlet-data-table-active-sort-color, var(--color-brand-accent));
1579
+ }
1580
+
1581
+ /* Danger action: tinted with the feedback-danger token so destructive
1582
+ actions read as deliberate without screaming. */
1583
+ .crewlet-data-table__row-actions-item--danger {
1584
+ color: var(--color-feedback-danger);
1585
+ }
1586
+
1587
+ .crewlet-data-table__row-actions-item--danger .crewlet-data-table__row-actions-item-icon {
1588
+ color: var(--color-feedback-danger);
1589
+ }
1590
+
1591
+ .crewlet-data-table__row-actions-item--danger:hover:not(.is-disabled) {
1592
+ background: rgba(239, 68, 68, 0.10);
1593
+ }