@kouji-ui/components 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/fesm2022/kouji-ui-components.mjs +49 -3
  2. package/fesm2022/kouji-ui-components.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/src/accordion/accordion.css +68 -0
  5. package/src/action-sheet/action-sheet.css +79 -0
  6. package/src/alert/alert.css +133 -0
  7. package/src/avatar/avatar.css +93 -0
  8. package/src/badge/badge.css +59 -0
  9. package/src/breadcrumb/breadcrumb.css +145 -0
  10. package/src/button/button.css +295 -0
  11. package/src/button-group/button-group.css +114 -0
  12. package/src/calendar/calendar.css +129 -0
  13. package/src/card/card.css +120 -0
  14. package/src/carousel/carousel.css +181 -0
  15. package/src/cascade-select/cascade-select.css +120 -0
  16. package/src/chart/chart.css +26 -0
  17. package/src/chat/chat-ai.css +352 -0
  18. package/src/chat/chat.css +231 -0
  19. package/src/checkbox/checkbox.css +74 -0
  20. package/src/color-picker/color-picker.css +183 -0
  21. package/src/combobox/combobox.css +101 -0
  22. package/src/command-palette/command-palette.css +234 -0
  23. package/src/confirm-popup/confirm-popup.css +47 -0
  24. package/src/date-picker/date-picker.css +61 -0
  25. package/src/date-range-presets/date-range-presets.css +49 -0
  26. package/src/datetime-picker/datetime-picker.css +84 -0
  27. package/src/dialog/dialog.css +56 -0
  28. package/src/direction-toggle/direction-toggle.css +55 -0
  29. package/src/divider/divider.css +93 -0
  30. package/src/drawer/drawer.css +130 -0
  31. package/src/dropdown-menu/dropdown-menu.css +108 -0
  32. package/src/editor/editor.css +140 -0
  33. package/src/empty-state/empty-state.css +114 -0
  34. package/src/field/field.css +148 -0
  35. package/src/file-upload/file-upload.css +207 -0
  36. package/src/form/form.css +63 -0
  37. package/src/input/input.css +70 -0
  38. package/src/input-group/input-group.css +125 -0
  39. package/src/input-mask/input-mask.css +39 -0
  40. package/src/input-otp/input-otp.css +87 -0
  41. package/src/kbd/kbd.css +67 -0
  42. package/src/link/link.css +104 -0
  43. package/src/list/list.css +166 -0
  44. package/src/menubar/menubar.css +62 -0
  45. package/src/number-input/number-input.css +115 -0
  46. package/src/overflow/overflow.css +43 -0
  47. package/src/overlay/overlay.css +45 -0
  48. package/src/overlay-badge/overlay-badge.css +118 -0
  49. package/src/pagination/pagination.css +146 -0
  50. package/src/password-input/password-input.css +155 -0
  51. package/src/popover/popover.css +71 -0
  52. package/src/progress-bar/progress-bar.css +113 -0
  53. package/src/radio/radio.css +58 -0
  54. package/src/rich-text/rich-text-editor.css +351 -0
  55. package/src/select/select.css +91 -0
  56. package/src/sheet/sheet.css +121 -0
  57. package/src/skeleton/skeleton.css +111 -0
  58. package/src/skip-link/skip-link.css +58 -0
  59. package/src/slider/slider.css +172 -0
  60. package/src/speed-dial/speed-dial.css +137 -0
  61. package/src/spinner/spinner.css +161 -0
  62. package/src/stepper/stepper.css +147 -0
  63. package/src/table/table-filters/filters.css +53 -0
  64. package/src/table/table.css +509 -0
  65. package/src/tabs/tabs.css +162 -0
  66. package/src/tag/tag.css +183 -0
  67. package/src/textarea/textarea.css +117 -0
  68. package/src/time-picker/time-picker.css +126 -0
  69. package/src/toast/toast.css +115 -0
  70. package/src/toggle/toggle.css +123 -0
  71. package/src/tooltip/tooltip.css +61 -0
  72. package/src/tree-select/tree-select.css +220 -0
  73. package/types/kouji-ui-components.d.ts +19 -0
@@ -0,0 +1,53 @@
1
+ /*
2
+ * Filter-row layout. All sizing flows from `kjSize="xs"` applied to the inner
3
+ * primitives (kj-input, kj-number-input, kj-select, kj-date-picker) — no more
4
+ * CSS-var override fakery here. This file owns the *layout* of the filter
5
+ * shells (flex direction, gap, child width), nothing else.
6
+ */
7
+
8
+ .kj-text-filter,
9
+ .kj-number-filter,
10
+ .kj-date-filter,
11
+ .kj-select-filter {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: var(--kj-base-space-xs, 0.25rem);
15
+ width: 100%;
16
+ }
17
+
18
+ /* The text filter wraps its <kj-input> in a <label>; let the label own the
19
+ row so the visually-hidden caption stays in flow without inflating width. */
20
+ .kj-text-filter__label,
21
+ .kj-select-filter__label {
22
+ display: flex;
23
+ width: 100%;
24
+ align-items: center;
25
+ }
26
+
27
+ /* Number filter: operator picker keeps natural width, each number input
28
+ takes equal share of the rest. */
29
+ .kj-number-filter__operator {
30
+ flex: 0 0 auto;
31
+ min-width: 4.25rem;
32
+ }
33
+ .kj-number-filter__input {
34
+ flex: 1 1 0;
35
+ min-width: 0;
36
+ }
37
+
38
+ /* Date filter: both pickers split the row evenly. */
39
+ .kj-date-filter__label {
40
+ display: flex;
41
+ flex: 1 1 0;
42
+ min-width: 0;
43
+ align-items: center;
44
+ }
45
+ .kj-date-filter .kj-date-picker {
46
+ flex: 1 1 0;
47
+ min-width: 0;
48
+ }
49
+
50
+ /* Select filter: trigger fills the cell. */
51
+ .kj-select-filter .kj-select {
52
+ width: 100%;
53
+ }
@@ -0,0 +1,509 @@
1
+ /* Density + variant tokens. All spacing via --kj-base-space-*.
2
+ *
3
+ * Sizing contract: the table is container-driven. If a parent gives `<kj-table>`
4
+ * a fixed height (`height: 400px`, a flex/grid track, etc.) the body fills
5
+ * the remaining space inside the wrapper and the row-scroll happens INSIDE
6
+ * `.kj-table-body`. If no parent constraint is set, the table grows to its
7
+ * natural content height — no internal scroll. `min-height: 0` / `min-width: 0`
8
+ * are the magic flex pieces that let the body actually shrink past content
9
+ * to honour an outer constraint. The same applies for width: the wrapper
10
+ * fills the host's width and the body scrolls horizontally when columns
11
+ * overflow.
12
+ */
13
+ :host {
14
+ display: flex;
15
+ flex-direction: column;
16
+ min-height: 0;
17
+ min-width: 0;
18
+ font-family: var(--kj-font-sans);
19
+ font-size: var(--kj-text-sm);
20
+ /* Default row height — predictable + virtualization-friendly. Density
21
+ variants below override; consumers can also write the var directly
22
+ (`<kj-table style="--kj-table-row-height: 2.75rem">…</kj-table>`). */
23
+ --kj-table-row-height: var(--kj-row-h);
24
+ }
25
+ :host([data-density="compact"]) { --kj-table-row-height: 1.75rem; }
26
+ :host([data-density="comfortable"]) { --kj-table-row-height: 2.75rem; }
27
+
28
+ .kj-table-wrapper {
29
+ display: flex;
30
+ flex-direction: column;
31
+ flex: 1 1 auto;
32
+ min-height: 0;
33
+ min-width: 0;
34
+ position: relative;
35
+ }
36
+
37
+ .kj-table-body {
38
+ position: relative;
39
+ overflow: auto;
40
+ /* Use block layout (not flex) for the body so the `<table>` keeps its
41
+ natural row-height behaviour. With flex, a child `<table>` stretched
42
+ to fill the body — that made a single row balloon to fill the entire
43
+ viewport when filters narrowed the result set. Block layout lets the
44
+ table size to its content; the body's `overflow: auto` still scrolls
45
+ when there are many rows, and the leftover space appears below the
46
+ last row when there are few. */
47
+ display: block;
48
+ flex: 1 1 auto;
49
+ min-height: 0;
50
+ min-width: 0;
51
+ }
52
+
53
+ /* Sticky header — the scroll container is `.kj-table-body`, which holds the
54
+ whole <table> including <thead>. Pin the header rows to the top of that
55
+ scrollport so only the rows scroll. Opaque background required (rows pass
56
+ underneath); z-index above pinned data cells (1) so corner cells layer
57
+ correctly, below the resize indicator (3). */
58
+ .kj-table-body thead th {
59
+ position: sticky;
60
+ top: 0;
61
+ z-index: 2;
62
+ /* Themable header band — falls back to the table surface. */
63
+ background: var(--kj-table-header-bg, var(--kj-bg-surface));
64
+ }
65
+ .kj-table-body thead th[data-pin] {
66
+ z-index: 3;
67
+ }
68
+
69
+ .kj-table-body > table {
70
+ /* Table never flex-grows; row heights stay content-driven (or row-height
71
+ CSS var-driven if the consumer sets `--kj-table-row-height`). */
72
+ min-width: 100%;
73
+ }
74
+
75
+ :host th,
76
+ :host td {
77
+ height: var(--kj-table-row-height);
78
+ }
79
+
80
+ :host table {
81
+ width: 100%;
82
+ border-collapse: collapse;
83
+ background: var(--kj-bg-surface);
84
+ color: var(--kj-fg-default);
85
+ }
86
+
87
+ :host th,
88
+ :host td {
89
+ text-align: left;
90
+ padding: var(--kj-base-space-sm) var(--kj-base-space-md);
91
+ border-bottom: 1px solid var(--kj-border-default);
92
+ position: relative;
93
+ }
94
+
95
+ :host th {
96
+ font-weight: 600;
97
+ background: var(--kj-bg-elevated);
98
+ }
99
+
100
+ /* ── Sort indicator ─────────────────────────────────────────────
101
+ The wrapper template renders a `<i kjIcon>` after the header label
102
+ when the column is sortable. CSS here just owns layout + tint:
103
+ - default → muted (unsorted/sortable)
104
+ - data-sort=… → brand primary
105
+ The icon sits inline so it survives the resize handle on the right
106
+ edge of the cell. */
107
+ :host th[aria-sort] {
108
+ user-select: none;
109
+ }
110
+ .kj-table-sort-indicator {
111
+ display: inline-flex;
112
+ align-items: center;
113
+ margin-inline-start: var(--kj-base-space-xs, 0.25rem);
114
+ color: var(--kj-fg-subtle, var(--kj-fg-muted));
115
+ transition: color var(--kj-transition);
116
+ vertical-align: middle;
117
+ }
118
+ :host th[data-sort="asc"] .kj-table-sort-indicator,
119
+ :host th[data-sort="desc"] .kj-table-sort-indicator {
120
+ color: var(--kj-fg-primary);
121
+ }
122
+ :host th[aria-sort]:hover .kj-table-sort-indicator,
123
+ :host th[aria-sort]:focus-visible .kj-table-sort-indicator {
124
+ color: var(--kj-fg-default);
125
+ }
126
+
127
+ :host([data-density="compact"]) th,
128
+ :host([data-density="compact"]) td {
129
+ padding: var(--kj-base-space-xs) var(--kj-base-space-sm);
130
+ }
131
+
132
+ :host([data-density="comfortable"]) th,
133
+ :host([data-density="comfortable"]) td {
134
+ padding: var(--kj-base-space-md) var(--kj-base-space-lg);
135
+ }
136
+
137
+ :host([data-variant="striped"]) tbody tr:nth-child(even) td {
138
+ background: var(--kj-bg-elevated);
139
+ }
140
+
141
+ :host([data-variant="clean"]) th,
142
+ :host([data-variant="clean"]) td {
143
+ border-bottom-color: transparent;
144
+ }
145
+
146
+ :host td:focus-visible,
147
+ :host th:focus-visible {
148
+ outline: 2px solid var(--kj-border-focus);
149
+ outline-offset: -2px;
150
+ }
151
+
152
+ /* Inline cell editors must NOT push the column wider than its existing
153
+ content. A bare `<input>` (and the styled select trigger) has an
154
+ intrinsic min-content width — ~20 chars from `size` for inputs, the
155
+ selected label for triggers — that table auto-layout uses to size the
156
+ column.
157
+
158
+ Trick: render an invisible "ghost" of the cell's value so the cell
159
+ keeps its natural content width, then lift every editor host out of
160
+ the in-flow stacking context with `position: absolute`. Editors use
161
+ `display: contents` by default — we override that while editing so
162
+ they actually get a box that can be positioned. */
163
+ :host td.kj-table-cell--editing {
164
+ position: relative;
165
+ }
166
+ :host td .kj-table-cell-ghost {
167
+ visibility: hidden;
168
+ white-space: pre;
169
+ display: inline-block;
170
+ pointer-events: none;
171
+ }
172
+ /* `::ng-deep` is required because the editor components are created
173
+ dynamically via `ViewContainerRef.createComponent` from inside the
174
+ `kjCellEditorOutlet` directive. Dynamic elements don't inherit the
175
+ parent template's emulated-encapsulation attributes, so a scoped
176
+ selector wouldn't match. */
177
+ /* `!important` is needed to override the editor / input components'
178
+ inline `style="display: contents"` host bindings (those win over
179
+ stylesheets). `::ng-deep` reaches the dynamically-created editor
180
+ components, which don't inherit the parent template's emulated
181
+ encapsulation attributes. */
182
+ :host td.kj-table-cell--editing ::ng-deep kj-text-editor,
183
+ :host td.kj-table-cell--editing ::ng-deep kj-number-editor,
184
+ :host td.kj-table-cell--editing ::ng-deep kj-select-editor,
185
+ :host td.kj-table-cell--editing ::ng-deep kj-date-editor,
186
+ :host td.kj-table-cell--editing ::ng-deep kj-boolean-editor {
187
+ position: absolute !important;
188
+ inset: 0 !important;
189
+ display: flex !important;
190
+ align-items: center !important;
191
+ padding: inherit !important;
192
+ background: var(--kj-bg-surface);
193
+ z-index: 1;
194
+ }
195
+ :host td.kj-table-cell--editing ::ng-deep kj-input,
196
+ :host td.kj-table-cell--editing ::ng-deep kj-number-input,
197
+ :host td.kj-table-cell--editing ::ng-deep kj-select {
198
+ display: flex !important;
199
+ width: 100% !important;
200
+ min-width: 0 !important;
201
+ box-sizing: border-box;
202
+ }
203
+ :host td.kj-table-cell--editing ::ng-deep input,
204
+ :host td.kj-table-cell--editing ::ng-deep .kj-select-trigger {
205
+ width: 100% !important;
206
+ min-width: 0 !important;
207
+ box-sizing: border-box;
208
+ }
209
+ /* The anchor span itself contributes nothing to layout — the directive
210
+ creates the editor as its NEXT SIBLING in the cell, not as a child. */
211
+ :host td .kj-table-editor-anchor {
212
+ display: none;
213
+ }
214
+
215
+ /* Selected rows get a brand-accent tint + a left border stripe so a
216
+ keyboard user can tell which row is selected at a glance, even when
217
+ the row is not focused. */
218
+ :host tr[data-selected] td {
219
+ background: color-mix(in oklab, var(--kj-bg-primary, var(--kj-bg-accent, currentColor)) 12%, var(--kj-bg-surface));
220
+ }
221
+ :host tr[data-selected] td:first-child {
222
+ box-shadow: inset 3px 0 0 0 var(--kj-bg-primary, var(--kj-fg-primary));
223
+ }
224
+
225
+ /* ── Pinned columns ─────────────────────────────────────────────
226
+ Sticky positioning + a visible boundary (border + soft shadow) so the
227
+ pinned edge reads as a separate region from the scrolling middle. The
228
+ :host([data-variant]) toggles don't affect pin borders — they're a
229
+ structural cue, not a visual variant. */
230
+ :host td[data-pin="left"],
231
+ :host th[data-pin="left"] {
232
+ position: sticky;
233
+ left: 0;
234
+ z-index: 1;
235
+ background: var(--kj-bg-surface);
236
+ }
237
+ :host td[data-pin="right"],
238
+ :host th[data-pin="right"] {
239
+ position: sticky;
240
+ right: 0;
241
+ z-index: 1;
242
+ background: var(--kj-bg-surface);
243
+ }
244
+
245
+ /* Boundary indicator on the inside edge of each pinned region.
246
+
247
+ Last LEFT-pinned cell: a `[data-pin="left"]` whose next sibling is NOT
248
+ also left-pinned — uses `:not(:has(+ ...))`.
249
+
250
+ First RIGHT-pinned cell: a `[data-pin="right"]` whose previous sibling
251
+ is NOT right-pinned — uses the `:not([..]) + [data-pin="right"]` pattern.
252
+
253
+ Both are purely declarative; no JS bookkeeping. */
254
+ :host td[data-pin="left"]:not(:has(+ td[data-pin="left"])),
255
+ :host th[data-pin="left"]:not(:has(+ th[data-pin="left"])) {
256
+ border-right: 1px solid var(--kj-border-strong, var(--kj-border-default));
257
+ box-shadow: 4px 0 6px -4px rgba(0, 0, 0, 0.25);
258
+ }
259
+ :host tr > :not([data-pin="right"]) + td[data-pin="right"],
260
+ :host tr > :not([data-pin="right"]) + th[data-pin="right"] {
261
+ border-left: 1px solid var(--kj-border-strong, var(--kj-border-default));
262
+ box-shadow: -4px 0 6px -4px rgba(0, 0, 0, 0.25);
263
+ }
264
+
265
+ /* Striped variant: pinned cells still get the alternate background so the
266
+ sticky cell doesn't visually float over a coloured row beneath it. */
267
+ :host([data-variant="striped"]) tbody tr:nth-child(even) td[data-pin] {
268
+ background: var(--kj-bg-elevated);
269
+ }
270
+
271
+ /* ── Resize handle ──────────────────────────────────────────────
272
+ Always-visible thin track on the right edge of each `<th>`; widens
273
+ and brightens to the focus color on hover/focus so users can tell
274
+ it's draggable. Hidden when no resize is enabled (the host doesn't
275
+ render the element). */
276
+ .kj-table-resize-handle {
277
+ position: absolute;
278
+ top: 25%;
279
+ right: 0;
280
+ width: 6px;
281
+ height: 50%;
282
+ cursor: col-resize;
283
+ user-select: none;
284
+ touch-action: none;
285
+ background: transparent;
286
+ border-right: 2px solid var(--kj-border-strong, var(--kj-border-default));
287
+ transition: border-color var(--kj-transition), background var(--kj-transition), width var(--kj-transition);
288
+ }
289
+ .kj-table-resize-handle:hover,
290
+ .kj-table-resize-handle:focus-visible {
291
+ border-right-color: var(--kj-border-focus);
292
+ background: color-mix(in srgb, var(--kj-border-focus) 20%, transparent);
293
+ width: 8px;
294
+ outline: none;
295
+ }
296
+
297
+ /* ── Resize preview guide ───────────────────────────────────────
298
+ While a column is being resized (TanStack `onEnd` mode), the
299
+ wrapper sets `data-resizing` on the header and pipes the live
300
+ cursor delta into `--kj-resize-delta`. The handle becomes a
301
+ tall vertical line anchored to the column's current right edge
302
+ and translated by the delta — it visually tracks where the new
303
+ edge will land before the column actually reflows on mouseup.
304
+
305
+ `top: 0; height: 9999px` overflows the `<th>`; the surrounding
306
+ `.kj-table-body` has `overflow: auto`, so the visible portion
307
+ is naturally clipped to the table's height. */
308
+ :host th[data-resizing] .kj-table-resize-handle {
309
+ top: 0;
310
+ height: 9999px;
311
+ width: 0;
312
+ background: transparent;
313
+ border-right: 2px solid var(--kj-border-focus, var(--kj-bg-primary));
314
+ transform: translateX(var(--kj-resize-delta, 0px));
315
+ pointer-events: none;
316
+ z-index: 3;
317
+ transition: none;
318
+ }
319
+
320
+ /* ── Selection column ───────────────────────────────────────────
321
+ Leading checkbox column auto-rendered when `kjSelectionMode` is
322
+ `'single'` or `'multi'`. Narrow fixed-width column so the row
323
+ data starts at a predictable horizontal position. */
324
+ :host th.kj-table-select-cell,
325
+ :host td.kj-table-select-cell {
326
+ width: 2.5rem;
327
+ padding-inline: var(--kj-base-space-sm);
328
+ text-align: center;
329
+ vertical-align: middle;
330
+ }
331
+ .kj-table-select-cell kj-checkbox {
332
+ display: inline-flex;
333
+ align-items: center;
334
+ justify-content: center;
335
+ }
336
+
337
+ /* ── Filter row beneath the header row ──────────────────────────
338
+ Each cell hosts a styled filter input (text / number / date / select)
339
+ mounted by the wrapper. We keep the row visually distinct from headers
340
+ with a lighter background + thinner padding, and let the input fill
341
+ the cell width so filters feel column-anchored. */
342
+ .kj-table-filter-row > th {
343
+ font-weight: 400;
344
+ background: var(--kj-bg-surface);
345
+ border-top: 1px solid var(--kj-border-muted, var(--kj-border-default));
346
+ }
347
+ .kj-table-filter-cell {
348
+ padding-top: var(--kj-base-space-xs);
349
+ padding-bottom: var(--kj-base-space-xs);
350
+ vertical-align: middle;
351
+ }
352
+ .kj-table-filter-cell .kj-table-filter-anchor + *,
353
+ .kj-table-filter-cell kj-input,
354
+ .kj-table-filter-cell kj-number-input,
355
+ .kj-table-filter-cell kj-date-picker,
356
+ .kj-table-filter-cell kj-select {
357
+ width: 100%;
358
+ }
359
+ .kj-table-filter-anchor {
360
+ display: block;
361
+ width: 100%;
362
+ }
363
+
364
+ /* Inline editors get the same compact xs treatment as filter cells. The
365
+ filter inputs already bind `kjSize="xs"` themselves; this rule covers the
366
+ editor anchors that mount unsized primitives at runtime. */
367
+ :host .kj-table-editor-anchor .kj-input,
368
+ :host .kj-table-editor-anchor .kj-select {
369
+ --kj-input-height: 1.75rem;
370
+ --kj-input-font-size: var(--kj-text-xs);
371
+ --kj-input-padding-x: var(--kj-base-space-sm);
372
+ --kj-input-padding-y: 0;
373
+ --kj-input-radius: var(--kj-radius-selector);
374
+ }
375
+
376
+ /* Empty / error / loading panes. */
377
+ .kj-table-empty,
378
+ .kj-table-error,
379
+ .kj-table-loading {
380
+ display: flex;
381
+ align-items: center;
382
+ justify-content: center;
383
+ padding: var(--kj-base-space-lg);
384
+ color: var(--kj-fg-muted);
385
+ }
386
+
387
+ /* ── Empty / error pane sizing ──────────────────────────────────
388
+ These panes are siblings *after* `.kj-table-body`, so with the
389
+ body's default `flex: 1 1 auto` a height-constrained table gave
390
+ the whole leftover column to the (row-less) body and left the
391
+ message stranded at the very bottom, reading as a footer rather
392
+ than a state.
393
+
394
+ When either state is active the body yields: it shrinks to its
395
+ content (the sticky header stays visible, keeping column context)
396
+ and the pane takes the residual space, so its existing
397
+ `align-items/justify-content: center` finally has a box to center
398
+ inside. Unconstrained tables are unaffected — there is no residual
399
+ space to hand over, and the pane keeps its natural padded height.
400
+
401
+ Loading is excluded: it is already an `inset: 0` overlay. */
402
+ :host([data-empty]) .kj-table-body,
403
+ :host([data-error]) .kj-table-body {
404
+ flex: 0 1 auto;
405
+ }
406
+ .kj-table-empty,
407
+ .kj-table-error {
408
+ flex: 1 1 auto;
409
+ min-height: 0;
410
+ }
411
+
412
+ /* ── Loading state ──────────────────────────────────────────────
413
+ Two modes:
414
+ - Initial load (`data-has-rows="false"`): full-pane overlay with
415
+ a centered "Loading…" message. The user has nothing to look at
416
+ yet so we cover the empty body.
417
+ - Subsequent reloads (`data-has-rows="true"`): a slim 2px brand
418
+ stripe at the top of the body. The stale rows stay readable
419
+ and pointer events keep working — the user can sort/scroll/
420
+ paginate again while the refresh is in flight. */
421
+ .kj-table-loading {
422
+ position: absolute;
423
+ inset: 0;
424
+ background: color-mix(in srgb, var(--kj-bg-surface) 70%, transparent);
425
+ pointer-events: none;
426
+ }
427
+ .kj-table-loading[data-has-rows] {
428
+ inset: 0 0 auto 0;
429
+ height: 2px;
430
+ background: var(--kj-bg-primary);
431
+ padding: 0;
432
+ animation: kj-table-loading-pulse 1.4s ease-in-out infinite;
433
+ }
434
+ .kj-table-loading[data-has-rows] .kj-table-loading-fallback,
435
+ .kj-table-loading[data-has-rows] ::ng-deep [kjLoading] {
436
+ display: none;
437
+ }
438
+
439
+ @keyframes kj-table-loading-pulse {
440
+ 0%, 100% { opacity: 0.55; }
441
+ 50% { opacity: 1; }
442
+ }
443
+ .kj-table-error {
444
+ color: var(--kj-fg-error, var(--kj-fg-default));
445
+ }
446
+ .kj-table-loading-fallback {
447
+ font-size: var(--kj-text-sm);
448
+ }
449
+
450
+ /* Row expansion. */
451
+ .kj-table-expansion-row > td {
452
+ background: var(--kj-bg-elevated);
453
+ }
454
+
455
+ /* ── Pinned-row tbody segments ──────────────────────────────────
456
+ Use the brand accent so pinned rows visually pop out of the center
457
+ body; sticky positioning lets them stay anchored at the top/bottom
458
+ even when the body scrolls. */
459
+ .kj-table-tbody-pinned[data-pin="top"] > tr > td {
460
+ background: var(--kj-bg-elevated);
461
+ border-bottom: 2px solid var(--kj-border-primary, var(--kj-border-strong, var(--kj-border-default)));
462
+ box-shadow: 0 4px 6px -4px rgba(0, 0, 0, 0.25);
463
+ position: sticky;
464
+ top: 0;
465
+ z-index: 2;
466
+ }
467
+ .kj-table-tbody-pinned[data-pin="bottom"] > tr > td {
468
+ background: var(--kj-bg-elevated);
469
+ border-top: 2px solid var(--kj-border-primary, var(--kj-border-strong, var(--kj-border-default)));
470
+ box-shadow: 0 -4px 6px -4px rgba(0, 0, 0, 0.25);
471
+ position: sticky;
472
+ bottom: 0;
473
+ z-index: 2;
474
+ }
475
+
476
+ /* ── Grouping row ───────────────────────────────────────────────
477
+ Rows where a column is grouped show a toggle-able header chip on the
478
+ grouped cell + count of subRows. */
479
+ .kj-table-group-toggle {
480
+ display: inline-flex;
481
+ align-items: center;
482
+ gap: var(--kj-base-space-xs, 0.25rem);
483
+ background: transparent;
484
+ border: 0;
485
+ padding: 0;
486
+ font: inherit;
487
+ color: inherit;
488
+ cursor: pointer;
489
+ }
490
+ .kj-table-group-toggle__chevron {
491
+ display: inline-block;
492
+ width: 0.75rem;
493
+ font-family: var(--kj-font-mono);
494
+ color: var(--kj-fg-muted);
495
+ transition: transform var(--kj-transition);
496
+ }
497
+ .kj-table-group-toggle[aria-expanded="true"] .kj-table-group-toggle__chevron {
498
+ transform: rotate(90deg);
499
+ }
500
+ .kj-table-group-toggle__count {
501
+ margin-inline-start: var(--kj-base-space-xs, 0.25rem);
502
+ font-size: 0.75em;
503
+ color: var(--kj-fg-muted);
504
+ font-variant-numeric: tabular-nums;
505
+ }
506
+ :host tbody tr[data-row-grouped] td {
507
+ background: var(--kj-bg-elevated);
508
+ font-weight: 600;
509
+ }