@kinetixui/angular 0.24.0 → 0.25.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.
- package/README.md +129 -0
- package/fesm2022/kinetixui-angular.mjs +4099 -189
- package/fesm2022/kinetixui-angular.mjs.map +1 -1
- package/package.json +4 -3
- package/styles.css +1720 -83
- package/types/kinetixui-angular.d.ts +1546 -7
package/styles.css
CHANGED
|
@@ -29,6 +29,16 @@
|
|
|
29
29
|
* tokens do, which is the entire point of the architecture.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
/* ── box model ──────────────────────────────────────────────────────────── */
|
|
33
|
+
/* Every size in this file is a border-box size, as it is in React (Tailwind's preflight sets it globally). An
|
|
34
|
+
Angular application has no such reset by default, and under content-box a 44px input with its padding and
|
|
35
|
+
border rendered 62px tall — and then did not grow at 200% text, because the extra 18px was already spent.
|
|
36
|
+
Scoped to KinetixUI's own classes and wrapped in :where() so it carries no specificity: an application's
|
|
37
|
+
own box-sizing rule, and any part here that opts out (the timeline marker), still win. */
|
|
38
|
+
:where([class^='kx-'], [class*=' kx-']),
|
|
39
|
+
:where([class^='kx-'], [class*=' kx-'])::before,
|
|
40
|
+
:where([class^='kx-'], [class*=' kx-'])::after { box-sizing: border-box; }
|
|
41
|
+
|
|
32
42
|
/* ── button ─────────────────────────────────────────────────────────────── */
|
|
33
43
|
.kx-btn {
|
|
34
44
|
display: inline-flex;
|
|
@@ -152,14 +162,61 @@
|
|
|
152
162
|
display: block;
|
|
153
163
|
background-color: hsl(var(--card));
|
|
154
164
|
color: hsl(var(--card-foreground));
|
|
155
|
-
|
|
165
|
+
/* The surface model (TOKENS.md): a soft edge at half the strength of the controls' stroke, and the md
|
|
166
|
+
elevation step for lift — the same resting card React renders. */
|
|
167
|
+
border: var(--border-width-default) solid hsl(var(--border) / 0.5);
|
|
156
168
|
border-radius: var(--radius-container);
|
|
157
|
-
box-shadow: var(--shadow-
|
|
169
|
+
box-shadow: var(--shadow-md);
|
|
170
|
+
}
|
|
171
|
+
/* The interactive card (TOKENS.md, "Card"): only on an element that IS interactive — `a[href]` or `button`
|
|
172
|
+
carrying kxCard — so a static card can never look clickable.
|
|
173
|
+
hover a pointer that can hover: edge to full `--border`, lifted to `--shadow-lg`
|
|
174
|
+
pressed `:active`: down to `--shadow-sm` with a `--muted` wash, so it differs from hover in depth AND fill
|
|
175
|
+
selected `aria-pressed="true"` or `aria-current`: a 2px `--primary` edge (the border plus an inset 1px) —
|
|
176
|
+
weight as well as hue — alongside the elevation of whichever state it is in
|
|
177
|
+
focus the shared focus ring replaces the elevation and is the strongest edge the card shows
|
|
178
|
+
box-shadow, border-color and background-color over `--duration-fast`; nothing moves or resizes. */
|
|
179
|
+
:where(a[href], button).kx-card {
|
|
180
|
+
display: block;
|
|
181
|
+
appearance: none;
|
|
182
|
+
padding: 0;
|
|
183
|
+
font: inherit;
|
|
184
|
+
text-align: start;
|
|
185
|
+
text-decoration: none;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
outline: none;
|
|
188
|
+
transition:
|
|
189
|
+
box-shadow var(--duration-fast) var(--easing-standard),
|
|
190
|
+
border-color var(--duration-fast) var(--easing-standard),
|
|
191
|
+
background-color var(--duration-fast) var(--easing-standard);
|
|
192
|
+
}
|
|
193
|
+
@media (hover: hover) {
|
|
194
|
+
:is(a[href], button).kx-card:hover { border-color: hsl(var(--border)); box-shadow: var(--shadow-lg); }
|
|
195
|
+
}
|
|
196
|
+
:is(a[href], button).kx-card:active { box-shadow: var(--shadow-sm); background-color: hsl(var(--muted) / 0.4); }
|
|
197
|
+
:is(a[href], button).kx-card:is([aria-pressed='true'], [aria-current]:not([aria-current='false'])) {
|
|
198
|
+
border-color: hsl(var(--primary));
|
|
199
|
+
box-shadow: inset 0 0 0 1px hsl(var(--primary)), var(--shadow-md);
|
|
200
|
+
}
|
|
201
|
+
@media (hover: hover) {
|
|
202
|
+
:is(a[href], button).kx-card:is([aria-pressed='true'], [aria-current]:not([aria-current='false'])):hover {
|
|
203
|
+
box-shadow: inset 0 0 0 1px hsl(var(--primary)), var(--shadow-lg);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
:is(a[href], button).kx-card:is([aria-pressed='true'], [aria-current]:not([aria-current='false'])):active {
|
|
207
|
+
box-shadow: inset 0 0 0 1px hsl(var(--primary)), var(--shadow-sm);
|
|
208
|
+
}
|
|
209
|
+
/* declared last at the highest specificity above, so no hover or selected shadow can hide it */
|
|
210
|
+
:is(a[href], button).kx-card:is(*, [aria-pressed], [aria-current]):focus-visible { box-shadow: var(--shadow-focus); }
|
|
211
|
+
button.kx-card:disabled { pointer-events: none; opacity: var(--opacity-disabled); }
|
|
212
|
+
@media (prefers-reduced-motion: reduce) {
|
|
213
|
+
:where(a[href], button).kx-card { transition: none; }
|
|
158
214
|
}
|
|
159
215
|
.kx-card-header { display: flex; flex-direction: column; gap: var(--spacing-1); padding: var(--spacing-6); }
|
|
160
216
|
.kx-card-title { font: var(--text-title-md); font-weight: var(--font-weight-semibold); margin: 0; }
|
|
161
217
|
.kx-card-description { font: var(--text-body-sm); color: hsl(var(--muted-foreground)); margin: 0; }
|
|
162
|
-
|
|
218
|
+
/* a custom element is inline by default, and an inline box drops its inline padding around the blocks inside it */
|
|
219
|
+
.kx-card-content { display: block; padding: 0 var(--spacing-6) var(--spacing-6); }
|
|
163
220
|
.kx-card-footer { display: flex; align-items: center; gap: var(--spacing-2); padding: 0 var(--spacing-6) var(--spacing-6); }
|
|
164
221
|
|
|
165
222
|
/* ── label ──────────────────────────────────────────────────────────────── */
|
|
@@ -172,7 +229,16 @@
|
|
|
172
229
|
/* the control is disabled, not the label — so the label follows via the group, never its own attribute */
|
|
173
230
|
.kx-label[data-disabled='true'] { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
174
231
|
|
|
175
|
-
/* ── input / textarea
|
|
232
|
+
/* ── input / textarea / native select ───────────────────────────────────── */
|
|
233
|
+
/* Text-entry state contract (TOKENS.md, "Text entry and navigation"), shared with React:
|
|
234
|
+
rest edge `--muted-foreground` at 80% (3.4:1 light / 5.4:1 dark on a card; `--input` was 2.2 / 2.4)
|
|
235
|
+
hover a pointer that can hover: full `--muted-foreground` — never over focus, invalid, read-only or
|
|
236
|
+
disabled, so hover cannot take over a stronger state
|
|
237
|
+
focus `--action` edge + `--shadow-focus`, the strongest signal
|
|
238
|
+
invalid `aria-invalid="true"` → `--destructive` edge and ring
|
|
239
|
+
read-only an inset `--muted` fill and full-strength text; no hover
|
|
240
|
+
disabled `--opacity-disabled`, inert
|
|
241
|
+
Motion: border-color, box-shadow and background-color over `--duration-instant`; none under reduce. */
|
|
176
242
|
.kx-input {
|
|
177
243
|
display: block;
|
|
178
244
|
inline-size: 100%;
|
|
@@ -180,11 +246,14 @@
|
|
|
180
246
|
padding: var(--spacing-2) var(--spacing-3);
|
|
181
247
|
background-color: hsl(var(--background));
|
|
182
248
|
color: hsl(var(--foreground));
|
|
183
|
-
border: var(--border-width-default) solid hsl(var(--
|
|
249
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground) / 0.8);
|
|
184
250
|
border-radius: var(--radius-field);
|
|
185
251
|
font: var(--text-body-md);
|
|
186
252
|
font-family: var(--font-family-sans);
|
|
187
|
-
transition:
|
|
253
|
+
transition:
|
|
254
|
+
border-color var(--duration-instant) var(--easing-standard),
|
|
255
|
+
box-shadow var(--duration-instant) var(--easing-standard),
|
|
256
|
+
background-color var(--duration-instant) var(--easing-standard);
|
|
188
257
|
}
|
|
189
258
|
.kx-input::placeholder { color: hsl(var(--muted-foreground)); }
|
|
190
259
|
.kx-input:focus-visible { outline: none; border-color: hsl(var(--ring)); box-shadow: var(--shadow-focus); }
|
|
@@ -192,55 +261,93 @@
|
|
|
192
261
|
/* aria-invalid is the source of truth for the error state — the styling follows the semantics, not the reverse */
|
|
193
262
|
.kx-input[aria-invalid='true'] { border-color: hsl(var(--destructive)); }
|
|
194
263
|
.kx-input[aria-invalid='true']:focus-visible { box-shadow: var(--shadow-focus-destructive); }
|
|
264
|
+
.kx-input[readonly],
|
|
265
|
+
.kx-textarea[readonly] { background-color: hsl(var(--muted)); }
|
|
266
|
+
@media (hover: hover) {
|
|
267
|
+
.kx-input:enabled:not(:focus):not([readonly]):not([aria-invalid='true']):hover,
|
|
268
|
+
.kx-textarea:enabled:not(:focus):not([readonly]):not([aria-invalid='true']):hover,
|
|
269
|
+
.kx-native-select:enabled:not(:focus):not([aria-invalid='true']):hover { border-color: hsl(var(--muted-foreground)); }
|
|
270
|
+
}
|
|
195
271
|
|
|
196
272
|
/* ── checkbox / switch ──────────────────────────────────────────────────── */
|
|
273
|
+
/* Selection-control state contract (TOKENS.md, "Selection controls"), shared with React:
|
|
274
|
+
rest the unchecked box's edge is `--muted-foreground` — it is the only thing that says a checkbox is
|
|
275
|
+
there, so it clears SC 1.4.11's 3:1, which `--input` (2.2:1) does not
|
|
276
|
+
hover a pointer that can hover: a `--foreground` state layer at 8% around the control; an unchecked
|
|
277
|
+
box's edge darkens to `--foreground`, a checked fill steps to `--primary` at 90%
|
|
278
|
+
pressed the state layer deepens to 14%
|
|
279
|
+
focus the shared focus shadow, declared after hover and pressed at the same specificity so neither
|
|
280
|
+
can cover it
|
|
281
|
+
disabled the hover and pressed layers are keyed on `:enabled`, so a disabled control never answers */
|
|
197
282
|
.kx-checkbox {
|
|
198
283
|
display: inline-grid;
|
|
199
284
|
place-items: center;
|
|
200
|
-
|
|
201
|
-
|
|
285
|
+
/* rem, not px: a selection control has no text of its own, so its box is what must grow at 200% text */
|
|
286
|
+
inline-size: 1.125rem;
|
|
287
|
+
block-size: 1.125rem;
|
|
202
288
|
flex-shrink: 0;
|
|
203
289
|
padding: 0;
|
|
204
290
|
background-color: hsl(var(--background));
|
|
205
|
-
color: hsl(var(--
|
|
206
|
-
border: var(--border-width-default) solid hsl(var(--
|
|
291
|
+
color: hsl(var(--action-foreground));
|
|
292
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground));
|
|
207
293
|
border-radius: var(--radius-sm);
|
|
208
294
|
cursor: pointer;
|
|
209
|
-
transition:
|
|
295
|
+
transition:
|
|
296
|
+
background-color var(--duration-instant) var(--easing-standard),
|
|
297
|
+
border-color var(--duration-instant) var(--easing-standard),
|
|
298
|
+
box-shadow var(--duration-instant) var(--easing-standard);
|
|
210
299
|
}
|
|
211
|
-
.kx-checkbox:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
212
300
|
.kx-checkbox[aria-checked='true'],
|
|
213
|
-
.kx-checkbox[aria-checked='mixed'] { background-color: hsl(var(--
|
|
301
|
+
.kx-checkbox[aria-checked='mixed'] { background-color: hsl(var(--action)); border-color: hsl(var(--action)); }
|
|
302
|
+
@media (hover: hover) {
|
|
303
|
+
.kx-checkbox:enabled:hover { box-shadow: 0 0 0 5px hsl(var(--foreground) / 0.08); }
|
|
304
|
+
.kx-checkbox:enabled:hover:not([aria-checked='true'], [aria-checked='mixed']) { border-color: hsl(var(--foreground)); }
|
|
305
|
+
.kx-checkbox:enabled:hover:is([aria-checked='true'], [aria-checked='mixed']) { background-color: hsl(var(--action) / 0.9); }
|
|
306
|
+
}
|
|
307
|
+
.kx-checkbox:enabled:active { box-shadow: 0 0 0 5px hsl(var(--foreground) / 0.14); }
|
|
308
|
+
.kx-checkbox:enabled:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
214
309
|
.kx-checkbox:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
215
|
-
.kx-checkbox > svg { inline-size:
|
|
310
|
+
.kx-checkbox > svg { inline-size: 0.875rem; block-size: 0.875rem; pointer-events: none; }
|
|
216
311
|
|
|
217
312
|
.kx-switch {
|
|
218
313
|
display: inline-flex;
|
|
219
314
|
align-items: center;
|
|
220
|
-
inline-size:
|
|
221
|
-
block-size:
|
|
315
|
+
inline-size: 2.75rem;
|
|
316
|
+
block-size: 1.5rem;
|
|
222
317
|
flex-shrink: 0;
|
|
223
|
-
padding:
|
|
318
|
+
padding: 0.125rem;
|
|
224
319
|
background-color: hsl(var(--tertiary));
|
|
225
320
|
border: var(--border-width-default) solid transparent;
|
|
226
321
|
border-radius: var(--radius-full);
|
|
227
322
|
cursor: pointer;
|
|
228
|
-
transition:
|
|
323
|
+
transition:
|
|
324
|
+
background-color var(--duration-base) var(--easing-standard),
|
|
325
|
+
box-shadow var(--duration-instant) var(--easing-standard);
|
|
229
326
|
}
|
|
230
|
-
.kx-switch
|
|
231
|
-
|
|
327
|
+
.kx-switch[aria-checked='true'] { background-color: hsl(var(--action)); }
|
|
328
|
+
@media (hover: hover) {
|
|
329
|
+
.kx-switch:enabled:hover { box-shadow: 0 0 0 5px hsl(var(--foreground) / 0.08); }
|
|
330
|
+
.kx-switch:enabled:hover[aria-checked='true'] { background-color: hsl(var(--action) / 0.9); }
|
|
331
|
+
}
|
|
332
|
+
.kx-switch:enabled:active { box-shadow: 0 0 0 5px hsl(var(--foreground) / 0.14); }
|
|
333
|
+
.kx-switch:enabled:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
232
334
|
.kx-switch:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
233
335
|
.kx-switch__thumb {
|
|
234
|
-
|
|
235
|
-
|
|
336
|
+
position: relative;
|
|
337
|
+
inset-inline-start: 0;
|
|
338
|
+
inline-size: 1.125rem;
|
|
339
|
+
block-size: 1.125rem;
|
|
236
340
|
border-radius: var(--radius-full);
|
|
237
341
|
background-color: hsl(var(--static-white));
|
|
238
342
|
box-shadow: var(--shadow-sm);
|
|
239
|
-
transition:
|
|
343
|
+
transition: inset-inline-start var(--duration-base) var(--easing-standard);
|
|
240
344
|
}
|
|
241
|
-
/*
|
|
242
|
-
|
|
243
|
-
|
|
345
|
+
/* The thumb travels along the inline axis of the switch's OWN direction: `inset-inline-start` is logical, so
|
|
346
|
+
the browser resolves it against the nearest `dir`. It used to be `translate` flipped by `[dir='rtl'] …`,
|
|
347
|
+
which matches ANY rtl ancestor — a switch in an LTR section of an RTL page was flipped too and its thumb
|
|
348
|
+
landed outside the track (check:selection-visual, "direction"). A logical property needs no selector, so
|
|
349
|
+
no build step can rewrite the direction test into a guess (see the React Switch). */
|
|
350
|
+
.kx-switch[aria-checked='true'] .kx-switch__thumb { inset-inline-start: 1.25rem; }
|
|
244
351
|
|
|
245
352
|
/* ── separator ──────────────────────────────────────────────────────────── */
|
|
246
353
|
.kx-separator { display: block; flex-shrink: 0; background-color: hsl(var(--border)); border: 0; margin: 0; }
|
|
@@ -292,6 +399,9 @@
|
|
|
292
399
|
.kx-tabs { display: block; }
|
|
293
400
|
.kx-tab-list {
|
|
294
401
|
display: inline-flex;
|
|
402
|
+
/* wraps inside its own width rather than running off a phone at 200% text (React's TabsList does the same) */
|
|
403
|
+
flex-wrap: wrap;
|
|
404
|
+
max-inline-size: 100%;
|
|
295
405
|
align-items: center;
|
|
296
406
|
gap: var(--spacing-1);
|
|
297
407
|
border-block-end: var(--border-width-default) solid hsl(var(--border));
|
|
@@ -306,10 +416,21 @@
|
|
|
306
416
|
font: var(--text-label-md);
|
|
307
417
|
font-weight: var(--font-weight-medium);
|
|
308
418
|
color: hsl(var(--muted-foreground));
|
|
419
|
+
border-start-start-radius: var(--radius-sm);
|
|
420
|
+
border-start-end-radius: var(--radius-sm);
|
|
309
421
|
cursor: pointer;
|
|
310
|
-
transition:
|
|
422
|
+
transition:
|
|
423
|
+
color var(--duration-fast) var(--easing-standard),
|
|
424
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
425
|
+
border-color var(--duration-fast) var(--easing-standard);
|
|
426
|
+
}
|
|
427
|
+
/* Navigation state contract (TOKENS.md, "Text entry and navigation"). Angular's strip is underlined rather
|
|
428
|
+
than React's segmented well — the shape differs, the semantics do not: hover is the selection controls'
|
|
429
|
+
8% `--foreground` layer on an unselected, enabled tab (a disabled one used to answer with a colour change);
|
|
430
|
+
selected is `--foreground` text AND the 2px `--primary` indicator; focus is the ring. */
|
|
431
|
+
@media (hover: hover) {
|
|
432
|
+
.kx-tab:enabled:not([aria-selected='true']):hover { color: hsl(var(--foreground)); background-color: hsl(var(--foreground) / 0.08); }
|
|
311
433
|
}
|
|
312
|
-
.kx-tab:hover { color: hsl(var(--foreground)); }
|
|
313
434
|
.kx-tab:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
314
435
|
.kx-tab[aria-selected='true'] { color: hsl(var(--foreground)); border-block-end-color: hsl(var(--primary)); }
|
|
315
436
|
.kx-tab:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
@@ -506,14 +627,17 @@
|
|
|
506
627
|
min-block-size: 100px;
|
|
507
628
|
resize: vertical;
|
|
508
629
|
padding: var(--spacing-3);
|
|
509
|
-
border: var(--border-width-default) solid hsl(var(--
|
|
630
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground) / 0.8);
|
|
510
631
|
border-radius: var(--radius-field);
|
|
511
632
|
background-color: hsl(var(--background));
|
|
512
633
|
font: var(--text-body-md);
|
|
513
634
|
font-family: var(--font-family-sans);
|
|
514
635
|
color: hsl(var(--foreground));
|
|
515
636
|
outline: none;
|
|
516
|
-
transition:
|
|
637
|
+
transition:
|
|
638
|
+
border-color var(--duration-instant) var(--easing-standard),
|
|
639
|
+
box-shadow var(--duration-instant) var(--easing-standard),
|
|
640
|
+
background-color var(--duration-instant) var(--easing-standard);
|
|
517
641
|
}
|
|
518
642
|
.kx-textarea::placeholder { color: hsl(var(--muted-foreground)); }
|
|
519
643
|
.kx-textarea:focus-visible { border-color: hsl(var(--action)); box-shadow: var(--shadow-focus); }
|
|
@@ -526,14 +650,20 @@
|
|
|
526
650
|
inline-size: 100%;
|
|
527
651
|
padding: var(--spacing-3);
|
|
528
652
|
padding-inline-end: var(--spacing-8);
|
|
529
|
-
border: var(--border-width-default) solid hsl(var(--
|
|
653
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground) / 0.8);
|
|
530
654
|
background-color: hsl(var(--background));
|
|
531
655
|
font: var(--text-body-md);
|
|
532
656
|
font-family: var(--font-family-sans);
|
|
533
657
|
color: hsl(var(--foreground));
|
|
534
658
|
outline: none;
|
|
535
|
-
transition:
|
|
659
|
+
transition:
|
|
660
|
+
border-color var(--duration-instant) var(--easing-standard),
|
|
661
|
+
box-shadow var(--duration-instant) var(--easing-standard);
|
|
536
662
|
}
|
|
663
|
+
/* The placeholder is the empty-valued option: while it is chosen the field reads as unanswered. The options
|
|
664
|
+
in the open picker keep the value colour. */
|
|
665
|
+
.kx-native-select:has(option[value='']:checked) { color: hsl(var(--muted-foreground)); }
|
|
666
|
+
.kx-native-select option { color: hsl(var(--foreground)); }
|
|
537
667
|
.kx-native-select--sharp { border-radius: var(--radius-none); }
|
|
538
668
|
.kx-native-select--default { border-radius: var(--radius-field); }
|
|
539
669
|
.kx-native-select--rounded { border-radius: var(--radius-md); }
|
|
@@ -541,6 +671,9 @@
|
|
|
541
671
|
.kx-native-select:focus-visible { border-color: hsl(var(--action)); box-shadow: var(--shadow-focus); }
|
|
542
672
|
.kx-native-select:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
543
673
|
.kx-native-select[aria-invalid='true'] { border-color: hsl(var(--destructive)); }
|
|
674
|
+
/* Input and Textarea already turned the ring destructive on an invalid field; the select drew the default
|
|
675
|
+
ring, so a focused invalid select lost its error colour to the focus colour. */
|
|
676
|
+
.kx-native-select[aria-invalid='true']:focus-visible { box-shadow: var(--shadow-focus-destructive); }
|
|
544
677
|
|
|
545
678
|
/* ── radio group ────────────────────────────────────────────────────────── */
|
|
546
679
|
.kx-radio-group { display: flex; flex-direction: column; gap: var(--spacing-2); }
|
|
@@ -550,68 +683,92 @@
|
|
|
550
683
|
.kx-radio__input {
|
|
551
684
|
appearance: none;
|
|
552
685
|
margin: 0;
|
|
553
|
-
|
|
554
|
-
|
|
686
|
+
/* 1.25rem is --spacing-5 at the default text size; the spacing scale is px, so it would not grow at 200% */
|
|
687
|
+
inline-size: 1.25rem;
|
|
688
|
+
block-size: 1.25rem;
|
|
555
689
|
flex-shrink: 0;
|
|
556
|
-
border: var(--border-width-default) solid hsl(var(--
|
|
690
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground));
|
|
557
691
|
border-radius: var(--radius-full);
|
|
558
692
|
background-color: hsl(var(--background));
|
|
559
693
|
cursor: pointer;
|
|
560
|
-
|
|
694
|
+
/* the selection-control contract (see checkbox): a 3:1 edge, an 8% / 14% state layer, focus on top */
|
|
695
|
+
--kx-radio-dot: 0 0 #0000;
|
|
696
|
+
--kx-radio-layer: 0 0 #0000;
|
|
697
|
+
box-shadow: var(--kx-radio-dot), var(--kx-radio-layer);
|
|
698
|
+
transition: border-color var(--duration-instant) var(--easing-standard), box-shadow var(--duration-instant) var(--easing-standard);
|
|
561
699
|
}
|
|
562
|
-
/* the dot is an inset ring rather than a pseudo-element, so it scales with the control
|
|
700
|
+
/* the dot is an inset ring rather than a pseudo-element, so it scales with the control — and it is its own
|
|
701
|
+
custom property so the hover and pressed layers add to it instead of replacing it */
|
|
563
702
|
.kx-radio__input:checked {
|
|
564
|
-
border-color: hsl(var(--
|
|
565
|
-
|
|
703
|
+
border-color: hsl(var(--action));
|
|
704
|
+
--kx-radio-dot: inset 0 0 0 0.25rem hsl(var(--action));
|
|
705
|
+
}
|
|
706
|
+
@media (hover: hover) {
|
|
707
|
+
.kx-radio__input:enabled:hover { --kx-radio-layer: 0 0 0 5px hsl(var(--foreground) / 0.08); }
|
|
708
|
+
.kx-radio__input:enabled:hover:not(:checked) { border-color: hsl(var(--foreground)); }
|
|
566
709
|
}
|
|
567
|
-
.kx-radio__input:
|
|
568
|
-
.kx-radio__input:
|
|
710
|
+
.kx-radio__input:enabled:active { --kx-radio-layer: 0 0 0 5px hsl(var(--foreground) / 0.14); }
|
|
711
|
+
.kx-radio__input:enabled:focus-visible { outline: none; --kx-radio-layer: var(--shadow-focus); }
|
|
569
712
|
.kx-radio__input:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
570
713
|
.kx-radio__label { font: var(--text-body-md); font-family: var(--font-family-sans); color: hsl(var(--foreground)); cursor: pointer; }
|
|
571
714
|
.kx-radio__input:disabled + .kx-radio__label { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
572
715
|
|
|
573
716
|
/* ── slider ─────────────────────────────────────────────────────────────── */
|
|
574
|
-
.kx-slider { display: block; inline-size: 100%; }
|
|
717
|
+
.kx-slider { position: relative; display: block; inline-size: 100%; }
|
|
718
|
+
/* The visible track and fill sit behind a transparent native track. They are placed with logical properties,
|
|
719
|
+
so the fill grows from the inline start of the slider's own direction (see KxSlider). */
|
|
720
|
+
.kx-slider__track {
|
|
721
|
+
position: absolute;
|
|
722
|
+
inset-inline: 0;
|
|
723
|
+
inset-block-start: 50%;
|
|
724
|
+
block-size: 0.5rem;
|
|
725
|
+
margin-block-start: -0.25rem;
|
|
726
|
+
overflow: hidden;
|
|
727
|
+
border-radius: var(--radius-full);
|
|
728
|
+
background-color: hsl(var(--muted));
|
|
729
|
+
pointer-events: none;
|
|
730
|
+
}
|
|
731
|
+
.kx-slider__fill {
|
|
732
|
+
position: absolute;
|
|
733
|
+
inset-block: 0;
|
|
734
|
+
inset-inline-start: 0;
|
|
735
|
+
inline-size: var(--kx-slider-fill, 0%);
|
|
736
|
+
background-color: hsl(var(--primary));
|
|
737
|
+
}
|
|
738
|
+
.kx-slider:has(.kx-slider__input:disabled) .kx-slider__track { opacity: var(--opacity-disabled); }
|
|
575
739
|
.kx-slider__input {
|
|
740
|
+
position: relative;
|
|
741
|
+
display: block;
|
|
576
742
|
appearance: none;
|
|
577
743
|
inline-size: 100%;
|
|
578
|
-
|
|
744
|
+
/* rem throughout (the px spacing scale's 20 / 8 at the default text size), so the hit area grows with text */
|
|
745
|
+
block-size: 1.25rem;
|
|
579
746
|
margin: 0;
|
|
580
747
|
background: transparent;
|
|
581
748
|
cursor: pointer;
|
|
582
749
|
}
|
|
583
|
-
/* the filled portion is a gradient stop driven by --kx-slider-fill, so the track follows the real value */
|
|
584
750
|
.kx-slider__input::-webkit-slider-runnable-track {
|
|
585
|
-
block-size:
|
|
751
|
+
block-size: 0.5rem;
|
|
586
752
|
border-radius: var(--radius-full);
|
|
587
|
-
background:
|
|
588
|
-
to right,
|
|
589
|
-
hsl(var(--primary)) var(--kx-slider-fill, 0%),
|
|
590
|
-
hsl(var(--muted)) var(--kx-slider-fill, 0%)
|
|
591
|
-
);
|
|
753
|
+
background: transparent;
|
|
592
754
|
}
|
|
593
755
|
.kx-slider__input::-moz-range-track {
|
|
594
|
-
block-size:
|
|
595
|
-
border-radius: var(--radius-full);
|
|
596
|
-
background-color: hsl(var(--muted));
|
|
597
|
-
}
|
|
598
|
-
.kx-slider__input::-moz-range-progress {
|
|
599
|
-
block-size: var(--spacing-2);
|
|
756
|
+
block-size: 0.5rem;
|
|
600
757
|
border-radius: var(--radius-full);
|
|
601
|
-
background
|
|
758
|
+
background: transparent;
|
|
602
759
|
}
|
|
603
760
|
.kx-slider__input::-webkit-slider-thumb {
|
|
604
761
|
appearance: none;
|
|
605
|
-
margin-block-start: -
|
|
606
|
-
inline-size:
|
|
607
|
-
block-size:
|
|
762
|
+
margin-block-start: -0.375rem;
|
|
763
|
+
inline-size: 1.25rem;
|
|
764
|
+
block-size: 1.25rem;
|
|
608
765
|
border: var(--border-width-default) solid hsl(var(--primary));
|
|
609
766
|
border-radius: var(--radius-full);
|
|
610
767
|
background-color: hsl(var(--background));
|
|
611
768
|
}
|
|
612
769
|
.kx-slider__input::-moz-range-thumb {
|
|
613
|
-
inline-size:
|
|
614
|
-
block-size:
|
|
770
|
+
inline-size: 1.25rem;
|
|
771
|
+
block-size: 1.25rem;
|
|
615
772
|
border: var(--border-width-default) solid hsl(var(--primary));
|
|
616
773
|
border-radius: var(--radius-full);
|
|
617
774
|
background-color: hsl(var(--background));
|
|
@@ -619,16 +776,47 @@
|
|
|
619
776
|
.kx-slider__input:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--radius-full); }
|
|
620
777
|
.kx-slider__input:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
621
778
|
|
|
779
|
+
/* ── composite fields: number input, password input ─────────────────────── */
|
|
780
|
+
/* The text-entry state contract (TOKENS.md, "Composite fields"), shared with React's NumberInput and
|
|
781
|
+
InputGroup: the wrapper is ONE field, and it reads its state from the input inside it.
|
|
782
|
+
rest edge `--muted-foreground` at 80% (3:1 on a card; `--input` was 2.2:1)
|
|
783
|
+
hover full `--muted-foreground` — never over focus, invalid or disabled
|
|
784
|
+
focus the INPUT focused → `--action` edge + `--shadow-focus`. A focused part button (the reveal toggle)
|
|
785
|
+
draws its own ring and leaves the wrapper at rest, so the ring says which part has focus
|
|
786
|
+
invalid `aria-invalid="true"` on the inner input (the `aria-invalid` input) → `--destructive`, kept under
|
|
787
|
+
the pointer and when focused
|
|
788
|
+
read-only `readonly` on the inner input (kx-number-input's `readonly`) → the inset `--muted` fill, no hover
|
|
789
|
+
disabled `--opacity-disabled` on the whole field, inert */
|
|
790
|
+
.kx-number-input,
|
|
791
|
+
.kx-password-input {
|
|
792
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground) / 0.8);
|
|
793
|
+
border-radius: var(--radius-field);
|
|
794
|
+
background-color: hsl(var(--background));
|
|
795
|
+
overflow: hidden;
|
|
796
|
+
transition:
|
|
797
|
+
border-color var(--duration-instant) var(--easing-standard),
|
|
798
|
+
box-shadow var(--duration-instant) var(--easing-standard);
|
|
799
|
+
}
|
|
800
|
+
@media (hover: hover) {
|
|
801
|
+
.kx-number-input:not(:has(input:is(:focus, :disabled, [readonly], [aria-invalid='true']))):hover,
|
|
802
|
+
.kx-password-input:not(:has(input:is(:focus, :disabled, [readonly], [aria-invalid='true']))):hover { border-color: hsl(var(--muted-foreground)); }
|
|
803
|
+
}
|
|
804
|
+
.kx-number-input:has(.kx-number-input__input[readonly]) { background-color: hsl(var(--muted)); }
|
|
805
|
+
.kx-number-input:has(.kx-number-input__input:focus),
|
|
806
|
+
.kx-password-input:has(.kx-password-input__input:focus) { border-color: hsl(var(--action)); box-shadow: var(--shadow-focus); }
|
|
807
|
+
/* after focus and at least as specific, so a focused invalid field keeps its destructive edge */
|
|
808
|
+
.kx-number-input:has(.kx-number-input__input[aria-invalid='true']),
|
|
809
|
+
.kx-password-input:has(.kx-password-input__input[aria-invalid='true']) { border-color: hsl(var(--destructive)); }
|
|
810
|
+
.kx-number-input:has(.kx-number-input__input[aria-invalid='true']:focus),
|
|
811
|
+
.kx-password-input:has(.kx-password-input__input[aria-invalid='true']:focus) { box-shadow: var(--shadow-focus-destructive); }
|
|
812
|
+
.kx-number-input:has(input:disabled),
|
|
813
|
+
.kx-password-input:has(input:disabled) { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
814
|
+
|
|
622
815
|
/* ── number input ───────────────────────────────────────────────────────── */
|
|
623
816
|
.kx-number-input {
|
|
624
817
|
display: inline-flex;
|
|
625
818
|
align-items: stretch;
|
|
626
|
-
border: var(--border-width-default) solid hsl(var(--input));
|
|
627
|
-
border-radius: var(--radius-field);
|
|
628
|
-
background-color: hsl(var(--background));
|
|
629
|
-
overflow: hidden;
|
|
630
819
|
}
|
|
631
|
-
.kx-number-input:focus-within { border-color: hsl(var(--action)); box-shadow: var(--shadow-focus); }
|
|
632
820
|
.kx-number-input__input {
|
|
633
821
|
inline-size: var(--spacing-16);
|
|
634
822
|
min-inline-size: 0;
|
|
@@ -654,20 +842,16 @@
|
|
|
654
842
|
font: var(--text-body-md);
|
|
655
843
|
cursor: pointer;
|
|
656
844
|
}
|
|
657
|
-
.kx-number-input__step:hover { color: hsl(var(--foreground)); }
|
|
658
|
-
|
|
845
|
+
.kx-number-input__step:enabled:hover { color: hsl(var(--foreground)); }
|
|
846
|
+
/* the field's own opacity already says disabled — a step dimmed again inside it would read as more disabled */
|
|
847
|
+
.kx-number-input__step:disabled { cursor: not-allowed; }
|
|
659
848
|
|
|
660
849
|
/* ── password input ─────────────────────────────────────────────────────── */
|
|
661
850
|
.kx-password-input {
|
|
662
851
|
display: inline-flex;
|
|
663
852
|
align-items: stretch;
|
|
664
853
|
inline-size: 100%;
|
|
665
|
-
border: var(--border-width-default) solid hsl(var(--input));
|
|
666
|
-
border-radius: var(--radius-field);
|
|
667
|
-
background-color: hsl(var(--background));
|
|
668
|
-
overflow: hidden;
|
|
669
854
|
}
|
|
670
|
-
.kx-password-input:focus-within { border-color: hsl(var(--action)); box-shadow: var(--shadow-focus); }
|
|
671
855
|
.kx-password-input__input {
|
|
672
856
|
flex: 1;
|
|
673
857
|
min-inline-size: 0;
|
|
@@ -689,8 +873,179 @@
|
|
|
689
873
|
font-family: var(--font-family-sans);
|
|
690
874
|
cursor: pointer;
|
|
691
875
|
}
|
|
692
|
-
.kx-password-input__reveal:hover { color: hsl(var(--foreground)); }
|
|
693
|
-
|
|
876
|
+
.kx-password-input__reveal:enabled:hover { color: hsl(var(--foreground)); }
|
|
877
|
+
/* inset: the wrapper clips with overflow:hidden, so an outer ring would be cut off at the field's edge */
|
|
878
|
+
.kx-password-input__reveal:focus-visible { outline: none; box-shadow: inset 0 0 0 2px hsl(var(--ring)); }
|
|
879
|
+
|
|
880
|
+
/* ── input group ────────────────────────────────────────────────────────── */
|
|
881
|
+
/* The same composite-field contract as the number and password inputs above, read from the group's input:
|
|
882
|
+
rest `--muted-foreground` at 80%; hover the full colour, never over focus, invalid, read-only or disabled;
|
|
883
|
+
the INPUT focused → `--action` edge and `--shadow-focus` on the shell (a focused add-on button draws its own
|
|
884
|
+
inset ring instead and leaves the shell at rest); `aria-invalid` → `--destructive`, kept under the pointer
|
|
885
|
+
and focus; `readonly` → the inset `--muted` fill; a disabled input → the whole group at `--opacity-disabled`. */
|
|
886
|
+
.kx-input-group {
|
|
887
|
+
display: flex;
|
|
888
|
+
align-items: center;
|
|
889
|
+
inline-size: 100%;
|
|
890
|
+
overflow: hidden;
|
|
891
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground) / 0.8);
|
|
892
|
+
border-radius: var(--radius-field);
|
|
893
|
+
background-color: hsl(var(--background));
|
|
894
|
+
font-family: var(--font-family-sans);
|
|
895
|
+
transition:
|
|
896
|
+
border-color var(--duration-instant) var(--easing-standard),
|
|
897
|
+
box-shadow var(--duration-instant) var(--easing-standard),
|
|
898
|
+
background-color var(--duration-instant) var(--easing-standard);
|
|
899
|
+
}
|
|
900
|
+
@media (hover: hover) {
|
|
901
|
+
.kx-input-group:not(:has(.kx-input-group__input:is(:focus, :disabled, [readonly], [aria-invalid='true']))):hover { border-color: hsl(var(--muted-foreground)); }
|
|
902
|
+
}
|
|
903
|
+
.kx-input-group:has(.kx-input-group__input[readonly]) { background-color: hsl(var(--muted)); }
|
|
904
|
+
.kx-input-group:has(.kx-input-group__input:focus) { border-color: hsl(var(--action)); box-shadow: var(--shadow-focus); }
|
|
905
|
+
.kx-input-group:has(.kx-input-group__input[aria-invalid='true']) { border-color: hsl(var(--destructive)); }
|
|
906
|
+
.kx-input-group:has(.kx-input-group__input[aria-invalid='true']:focus) { box-shadow: var(--shadow-focus-destructive); }
|
|
907
|
+
/* The field is disabled when its input is. An add-on button disabled on its own (Copy with nothing to copy) dims
|
|
908
|
+
only itself: the input beside it still takes typing, so the group must not read as disabled. */
|
|
909
|
+
.kx-input-group:has(.kx-input-group__input:disabled) { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
910
|
+
.kx-input-group:not(:has(.kx-input-group__input:disabled)) .kx-input-group__button:disabled { opacity: var(--opacity-disabled); }
|
|
911
|
+
.kx-input-group__input {
|
|
912
|
+
flex: 1 1 0;
|
|
913
|
+
/* an <input>'s min-content is its intrinsic width (~20 characters), and that is what it contributes when
|
|
914
|
+
an auto-sized track sizes the group; a zero base keeps the group from widening its column past a phone
|
|
915
|
+
viewport at 200% text. It still grows to fill the field. */
|
|
916
|
+
inline-size: 0;
|
|
917
|
+
min-inline-size: 0;
|
|
918
|
+
border: 0;
|
|
919
|
+
padding: var(--spacing-3);
|
|
920
|
+
background: transparent;
|
|
921
|
+
font: var(--text-body-md);
|
|
922
|
+
font-family: var(--font-family-sans);
|
|
923
|
+
color: hsl(var(--foreground));
|
|
924
|
+
outline: none;
|
|
925
|
+
}
|
|
926
|
+
.kx-input-group__input::placeholder { color: hsl(var(--muted-foreground)); }
|
|
927
|
+
.kx-input-group__input:disabled { cursor: not-allowed; }
|
|
928
|
+
.kx-input-group__addon {
|
|
929
|
+
display: flex;
|
|
930
|
+
flex-shrink: 0;
|
|
931
|
+
align-items: center;
|
|
932
|
+
gap: var(--spacing-2);
|
|
933
|
+
color: hsl(var(--muted-foreground));
|
|
934
|
+
}
|
|
935
|
+
.kx-input-group__addon--start { padding-inline-start: var(--spacing-3); }
|
|
936
|
+
.kx-input-group__addon--end { padding-inline-end: var(--spacing-3); }
|
|
937
|
+
.kx-input-group__addon > svg { inline-size: 1rem; block-size: 1rem; flex-shrink: 0; }
|
|
938
|
+
.kx-input-group__text {
|
|
939
|
+
padding-inline: var(--spacing-3);
|
|
940
|
+
font: var(--text-body-md);
|
|
941
|
+
font-family: var(--font-family-sans);
|
|
942
|
+
color: hsl(var(--muted-foreground));
|
|
943
|
+
white-space: nowrap;
|
|
944
|
+
user-select: none;
|
|
945
|
+
}
|
|
946
|
+
/* the divider is internal structure, deliberately quieter than the shell's edge */
|
|
947
|
+
.kx-input-group__button {
|
|
948
|
+
display: flex;
|
|
949
|
+
flex-shrink: 0;
|
|
950
|
+
align-self: stretch;
|
|
951
|
+
align-items: center;
|
|
952
|
+
gap: var(--spacing-2);
|
|
953
|
+
appearance: none;
|
|
954
|
+
margin: 0;
|
|
955
|
+
border: 0;
|
|
956
|
+
border-inline-start: var(--border-width-default) solid hsl(var(--input));
|
|
957
|
+
padding-inline: var(--spacing-3);
|
|
958
|
+
background-color: hsl(var(--muted));
|
|
959
|
+
font: var(--text-label-md);
|
|
960
|
+
font-family: var(--font-family-sans);
|
|
961
|
+
color: hsl(var(--foreground));
|
|
962
|
+
cursor: pointer;
|
|
963
|
+
outline: none;
|
|
964
|
+
transition: background-color var(--duration-instant) var(--easing-standard), box-shadow var(--duration-instant) var(--easing-standard);
|
|
965
|
+
}
|
|
966
|
+
/* the selection controls' `--foreground` state layer, as an inset shadow so it composes with the focus ring */
|
|
967
|
+
@media (hover: hover) {
|
|
968
|
+
.kx-input-group__button:enabled:hover { box-shadow: inset 0 0 0 100vmax hsl(var(--foreground) / 0.08); }
|
|
969
|
+
}
|
|
970
|
+
.kx-input-group__button:focus-visible { box-shadow: inset 0 0 0 2px hsl(var(--ring)); }
|
|
971
|
+
.kx-input-group__button:disabled { cursor: not-allowed; }
|
|
972
|
+
|
|
973
|
+
/* ── one-time code ──────────────────────────────────────────────────────── */
|
|
974
|
+
/* The cells are decoration of one transparent input laid over them (KxInputOtp). Every cell owns its whole
|
|
975
|
+
border and each after the first in a group is pulled back a pixel, so a group that wraps at 200% text is
|
|
976
|
+
still closed at the end it starts from (React's InputOTP learned this the hard way). The cells follow the
|
|
977
|
+
composite-field contract, read from the input: rest `--muted-foreground` at 80%; hover the full colour, not
|
|
978
|
+
while focused, invalid or disabled; the ACTIVE cell (where the next character goes) takes `--action` and the
|
|
979
|
+
focus shadow, drawn above its neighbours; `aria-invalid` → `--destructive` edges; disabled → dimmed, inert. */
|
|
980
|
+
.kx-input-otp {
|
|
981
|
+
position: relative;
|
|
982
|
+
display: inline-flex;
|
|
983
|
+
max-inline-size: 100%;
|
|
984
|
+
}
|
|
985
|
+
.kx-input-otp__cells { display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-2); }
|
|
986
|
+
.kx-input-otp__group { display: flex; flex-wrap: wrap; align-items: center; }
|
|
987
|
+
.kx-input-otp__slot {
|
|
988
|
+
position: relative;
|
|
989
|
+
display: flex;
|
|
990
|
+
align-items: center;
|
|
991
|
+
justify-content: center;
|
|
992
|
+
inline-size: 2.25rem;
|
|
993
|
+
block-size: 2.25rem;
|
|
994
|
+
border: var(--border-width-default) solid hsl(var(--muted-foreground) / 0.8);
|
|
995
|
+
background-color: hsl(var(--background));
|
|
996
|
+
font: var(--text-body-md);
|
|
997
|
+
font-family: var(--font-family-sans);
|
|
998
|
+
color: hsl(var(--foreground));
|
|
999
|
+
transition:
|
|
1000
|
+
border-color var(--duration-instant) var(--easing-standard),
|
|
1001
|
+
box-shadow var(--duration-instant) var(--easing-standard);
|
|
1002
|
+
}
|
|
1003
|
+
.kx-input-otp__slot:not(:first-child) { margin-inline-start: -1px; }
|
|
1004
|
+
.kx-input-otp__slot:first-child { border-start-start-radius: var(--radius-md); border-end-start-radius: var(--radius-md); }
|
|
1005
|
+
.kx-input-otp__slot:last-child { border-start-end-radius: var(--radius-md); border-end-end-radius: var(--radius-md); }
|
|
1006
|
+
@media (hover: hover) {
|
|
1007
|
+
.kx-input-otp:not(:has(.kx-input-otp__input:is(:focus, :disabled, [aria-invalid='true']))):hover .kx-input-otp__slot { border-color: hsl(var(--muted-foreground)); }
|
|
1008
|
+
}
|
|
1009
|
+
.kx-input-otp:has(.kx-input-otp__input[aria-invalid='true']) .kx-input-otp__slot { border-color: hsl(var(--destructive)); }
|
|
1010
|
+
.kx-input-otp__slot[data-active] { z-index: 1; border-color: hsl(var(--action)); box-shadow: var(--shadow-focus); }
|
|
1011
|
+
.kx-input-otp:has(.kx-input-otp__input[aria-invalid='true']) .kx-input-otp__slot[data-active] { border-color: hsl(var(--destructive)); box-shadow: var(--shadow-focus-destructive); }
|
|
1012
|
+
.kx-input-otp:has(.kx-input-otp__input:disabled) { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
1013
|
+
.kx-input-otp__caret { position: absolute; inline-size: 1px; block-size: 1rem; background-color: hsl(var(--foreground)); pointer-events: none; }
|
|
1014
|
+
.kx-input-otp__separator { inline-size: var(--spacing-3); block-size: 2px; border-radius: var(--radius-full); background-color: hsl(var(--muted-foreground)); }
|
|
1015
|
+
/* the real input: over the cells, receiving every keystroke, paste and autofill, painting nothing itself */
|
|
1016
|
+
.kx-input-otp__input {
|
|
1017
|
+
position: absolute;
|
|
1018
|
+
inset: 0;
|
|
1019
|
+
inline-size: 100%;
|
|
1020
|
+
block-size: 100%;
|
|
1021
|
+
margin: 0;
|
|
1022
|
+
padding: 0;
|
|
1023
|
+
border: 0;
|
|
1024
|
+
background: transparent;
|
|
1025
|
+
color: transparent;
|
|
1026
|
+
caret-color: transparent;
|
|
1027
|
+
font: var(--text-body-md);
|
|
1028
|
+
letter-spacing: -0.5em;
|
|
1029
|
+
outline: none;
|
|
1030
|
+
}
|
|
1031
|
+
.kx-input-otp__input::selection { background: transparent; }
|
|
1032
|
+
.kx-input-otp__input:disabled { cursor: not-allowed; }
|
|
1033
|
+
|
|
1034
|
+
/* ── rating ─────────────────────────────────────────────────────────────── */
|
|
1035
|
+
/* Each star is a real radio, visually hidden and kept in the tab order, painted through its label (KxRating).
|
|
1036
|
+
The stars are rem-sized, so they grow with the reader's text; a chosen or previewed star is `--warning`
|
|
1037
|
+
filled, an empty one a `--muted-foreground` outline (3:1 against the page, SC 1.4.11). */
|
|
1038
|
+
.kx-rating { display: inline-flex; align-items: center; gap: 0.125rem; }
|
|
1039
|
+
.kx-rating__item { position: relative; display: inline-flex; padding: 0.125rem; border-radius: var(--radius-sm); }
|
|
1040
|
+
label.kx-rating__item { cursor: pointer; }
|
|
1041
|
+
.kx-rating__radio { position: absolute; inset: 0; margin: 0; opacity: 0; pointer-events: none; }
|
|
1042
|
+
.kx-rating__star { inline-size: 1.25rem; block-size: 1.25rem; fill: transparent; stroke: hsl(var(--muted-foreground)); stroke-width: 1.5; stroke-linejoin: round; }
|
|
1043
|
+
.kx-rating--sm .kx-rating__star { inline-size: 1rem; block-size: 1rem; }
|
|
1044
|
+
.kx-rating--lg .kx-rating__star { inline-size: 1.5rem; block-size: 1.5rem; }
|
|
1045
|
+
.kx-rating__star--on { fill: hsl(var(--warning)); stroke: hsl(var(--warning)); }
|
|
1046
|
+
.kx-rating__item:has(.kx-rating__radio:focus-visible) { box-shadow: var(--shadow-focus); }
|
|
1047
|
+
.kx-rating[aria-disabled='true'] { opacity: var(--opacity-disabled); }
|
|
1048
|
+
.kx-rating[aria-disabled='true'] .kx-rating__item { cursor: not-allowed; }
|
|
694
1049
|
|
|
695
1050
|
/* ── field ──────────────────────────────────────────────────────────────── */
|
|
696
1051
|
.kx-field { display: flex; flex-direction: column; gap: var(--spacing-2); }
|
|
@@ -718,9 +1073,10 @@
|
|
|
718
1073
|
cursor: pointer;
|
|
719
1074
|
transition: background-color var(--duration-fast) var(--easing-standard), color var(--duration-fast) var(--easing-standard);
|
|
720
1075
|
}
|
|
721
|
-
|
|
722
|
-
.kx-toggle--
|
|
723
|
-
.kx-toggle--
|
|
1076
|
+
/* the square is rem (--spacing-8 / 10 / 12 at the default text size): it holds the label, so it grows with it */
|
|
1077
|
+
.kx-toggle--sm { min-inline-size: 2rem; block-size: 2rem; padding-inline: var(--spacing-2); }
|
|
1078
|
+
.kx-toggle--md { min-inline-size: 2.5rem; block-size: 2.5rem; padding-inline: var(--spacing-3); }
|
|
1079
|
+
.kx-toggle--lg { min-inline-size: 3rem; block-size: 3rem; padding-inline: var(--spacing-4); }
|
|
724
1080
|
.kx-toggle--outline { border-color: hsl(var(--input)); }
|
|
725
1081
|
.kx-toggle:hover { background-color: hsl(var(--muted)); }
|
|
726
1082
|
.kx-toggle:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
@@ -739,10 +1095,13 @@
|
|
|
739
1095
|
/* ── segmented control ──────────────────────────────────────────────────── */
|
|
740
1096
|
.kx-segmented {
|
|
741
1097
|
display: inline-flex;
|
|
1098
|
+
flex-wrap: wrap;
|
|
1099
|
+
max-inline-size: 100%;
|
|
742
1100
|
align-items: stretch;
|
|
743
1101
|
padding: var(--spacing-1);
|
|
744
1102
|
border-radius: var(--radius-md);
|
|
745
|
-
|
|
1103
|
+
/* an inset well; the chosen segment is a small raised surface on it (TOKENS.md, "Surface model") */
|
|
1104
|
+
background-color: hsl(var(--surface-grouped));
|
|
746
1105
|
}
|
|
747
1106
|
.kx-segmented__item { display: inline-flex; }
|
|
748
1107
|
.kx-segmented__radio { position: absolute; opacity: 0; pointer-events: none; }
|
|
@@ -757,12 +1116,27 @@
|
|
|
757
1116
|
font-weight: var(--font-weight-medium);
|
|
758
1117
|
color: hsl(var(--muted-foreground));
|
|
759
1118
|
cursor: pointer;
|
|
760
|
-
transition:
|
|
1119
|
+
transition:
|
|
1120
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
1121
|
+
color var(--duration-fast) var(--easing-standard),
|
|
1122
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
761
1123
|
}
|
|
1124
|
+
/* chosen: surface + half-strength edge + depth + text — the Card's raised contract at control scale, so it
|
|
1125
|
+
sits ABOVE its track in both themes (a `--background` segment sank below a dark `--muted` track) */
|
|
762
1126
|
.kx-segmented__radio:checked + .kx-segmented__label {
|
|
763
|
-
background-color: hsl(var(--
|
|
1127
|
+
background-color: hsl(var(--card));
|
|
1128
|
+
color: hsl(var(--foreground));
|
|
1129
|
+
box-shadow: inset 0 0 0 1px hsl(var(--border) / 0.5), var(--shadow-sm);
|
|
1130
|
+
}
|
|
1131
|
+
@media (hover: hover) {
|
|
1132
|
+
.kx-segmented__radio:enabled:not(:checked) + .kx-segmented__label:hover {
|
|
1133
|
+
background-color: hsl(var(--foreground) / 0.08);
|
|
1134
|
+
color: hsl(var(--foreground));
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
.kx-segmented__radio:enabled:not(:checked) + .kx-segmented__label:active {
|
|
1138
|
+
background-color: hsl(var(--foreground) / 0.14);
|
|
764
1139
|
color: hsl(var(--foreground));
|
|
765
|
-
box-shadow: var(--shadow-sm);
|
|
766
1140
|
}
|
|
767
1141
|
.kx-segmented__radio:focus-visible + .kx-segmented__label { box-shadow: var(--shadow-focus); }
|
|
768
1142
|
.kx-segmented__radio:disabled + .kx-segmented__label { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
@@ -778,5 +1152,1268 @@
|
|
|
778
1152
|
.kx-textarea,
|
|
779
1153
|
.kx-native-select,
|
|
780
1154
|
.kx-radio__input,
|
|
1155
|
+
.kx-checkbox,
|
|
1156
|
+
.kx-switch,
|
|
1157
|
+
.kx-switch__thumb,
|
|
1158
|
+
.kx-input,
|
|
1159
|
+
.kx-number-input,
|
|
1160
|
+
.kx-password-input,
|
|
1161
|
+
.kx-input-group,
|
|
1162
|
+
.kx-input-group__button,
|
|
1163
|
+
.kx-input-otp__slot,
|
|
1164
|
+
.kx-tab,
|
|
781
1165
|
.kx-tag__remove { transition: none; }
|
|
782
1166
|
}
|
|
1167
|
+
|
|
1168
|
+
/* ══ content wave ═════════════════════════════════════════════════════════
|
|
1169
|
+
banner · button group · circular progress · code block · description list
|
|
1170
|
+
fab · image · inform · list · marquee · page header · timeline
|
|
1171
|
+
|
|
1172
|
+
Logical properties throughout (padding-inline, inset-inline-start, margin-inline)
|
|
1173
|
+
so every surface mirrors under dir="rtl" without a second rule.
|
|
1174
|
+
════════════════════════════════════════════════════════════════════════ */
|
|
1175
|
+
|
|
1176
|
+
/* ── banner + inform: one intent palette, two containers ────────────────── */
|
|
1177
|
+
.kx-banner,
|
|
1178
|
+
.kx-inform {
|
|
1179
|
+
display: flex;
|
|
1180
|
+
align-items: flex-start;
|
|
1181
|
+
gap: var(--spacing-2);
|
|
1182
|
+
font: var(--text-body-sm);
|
|
1183
|
+
font-family: var(--font-family-sans);
|
|
1184
|
+
}
|
|
1185
|
+
.kx-banner {
|
|
1186
|
+
width: 100%;
|
|
1187
|
+
border-block-end: var(--border-width-default) solid transparent;
|
|
1188
|
+
padding: var(--spacing-3) var(--spacing-4);
|
|
1189
|
+
}
|
|
1190
|
+
.kx-banner--sticky { position: sticky; inset-block-start: 0; z-index: var(--z-index-sticky, 40); }
|
|
1191
|
+
.kx-inform { border-radius: var(--radius-md); padding: var(--spacing-3); }
|
|
1192
|
+
|
|
1193
|
+
.kx-banner--information { background-color: hsl(var(--info) / 0.1); color: hsl(var(--on-info-container)); border-color: hsl(var(--info) / 0.2); }
|
|
1194
|
+
.kx-banner--warning { background-color: hsl(var(--warning) / 0.15); color: hsl(var(--warning)); border-color: hsl(var(--warning) / 0.25); }
|
|
1195
|
+
.kx-banner--success { background-color: hsl(var(--success) / 0.15); color: hsl(var(--success)); border-color: hsl(var(--success) / 0.25); }
|
|
1196
|
+
.kx-banner--error { background-color: hsl(var(--destructive) / 0.1); color: hsl(var(--destructive)); border-color: hsl(var(--destructive) / 0.2); }
|
|
1197
|
+
.kx-banner--action { background-color: hsl(var(--foreground)); color: hsl(var(--background)); }
|
|
1198
|
+
.kx-inform--information { background-color: hsl(var(--info) / 0.1); color: hsl(var(--on-info-container)); }
|
|
1199
|
+
.kx-inform--warning { background-color: hsl(var(--warning) / 0.15); color: hsl(var(--warning)); }
|
|
1200
|
+
.kx-inform--success { background-color: hsl(var(--success) / 0.15); color: hsl(var(--success)); }
|
|
1201
|
+
.kx-inform--error { background-color: hsl(var(--destructive) / 0.1); color: hsl(var(--destructive)); }
|
|
1202
|
+
.kx-inform--action { background-color: hsl(var(--foreground)); color: hsl(var(--background)); }
|
|
1203
|
+
|
|
1204
|
+
.kx-banner__body {
|
|
1205
|
+
display: flex;
|
|
1206
|
+
flex: 1 1 0%;
|
|
1207
|
+
flex-wrap: wrap;
|
|
1208
|
+
align-items: center;
|
|
1209
|
+
justify-content: center;
|
|
1210
|
+
gap: var(--spacing-1) var(--spacing-3);
|
|
1211
|
+
min-width: 0;
|
|
1212
|
+
margin-inline: auto;
|
|
1213
|
+
max-width: 56rem;
|
|
1214
|
+
text-align: center;
|
|
1215
|
+
}
|
|
1216
|
+
.kx-inform__body { flex: 1 1 0%; min-width: 0; }
|
|
1217
|
+
.kx-inform__message { margin: 0; }
|
|
1218
|
+
.kx-inform__message:not(:only-child) { margin-block-end: var(--spacing-2); }
|
|
1219
|
+
|
|
1220
|
+
.kx-banner [kxBannerAction],
|
|
1221
|
+
.kx-inform [kxInformAction] {
|
|
1222
|
+
display: inline-flex;
|
|
1223
|
+
align-items: center;
|
|
1224
|
+
gap: var(--spacing-1);
|
|
1225
|
+
background: none;
|
|
1226
|
+
border: 0;
|
|
1227
|
+
padding: 0;
|
|
1228
|
+
color: inherit;
|
|
1229
|
+
font: var(--text-label-md);
|
|
1230
|
+
font-weight: var(--font-weight-medium);
|
|
1231
|
+
text-underline-offset: 2px;
|
|
1232
|
+
cursor: pointer;
|
|
1233
|
+
}
|
|
1234
|
+
.kx-banner [kxBannerAction]:hover,
|
|
1235
|
+
.kx-banner [kxBannerAction]:focus-visible,
|
|
1236
|
+
.kx-inform [kxInformAction]:hover,
|
|
1237
|
+
.kx-inform [kxInformAction]:focus-visible { text-decoration: underline; outline: none; }
|
|
1238
|
+
|
|
1239
|
+
.kx-banner__dismiss,
|
|
1240
|
+
.kx-inform__dismiss {
|
|
1241
|
+
flex-shrink: 0;
|
|
1242
|
+
background: none;
|
|
1243
|
+
border: 0;
|
|
1244
|
+
padding: 2px;
|
|
1245
|
+
margin: -2px;
|
|
1246
|
+
border-radius: 2px;
|
|
1247
|
+
color: inherit;
|
|
1248
|
+
opacity: var(--opacity-muted);
|
|
1249
|
+
cursor: pointer;
|
|
1250
|
+
transition: opacity var(--duration-fast) var(--easing-standard);
|
|
1251
|
+
}
|
|
1252
|
+
.kx-banner__dismiss:hover,
|
|
1253
|
+
.kx-banner__dismiss:focus-visible,
|
|
1254
|
+
.kx-inform__dismiss:hover,
|
|
1255
|
+
.kx-inform__dismiss:focus-visible { opacity: var(--opacity-visible); outline: var(--border-width-default) solid currentColor; }
|
|
1256
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1257
|
+
.kx-banner__dismiss, .kx-inform__dismiss { transition: none; }
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/* ── button group ───────────────────────────────────────────────────────── */
|
|
1261
|
+
.kx-button-group { display: flex; width: fit-content; align-items: stretch; }
|
|
1262
|
+
.kx-button-group > * { position: relative; }
|
|
1263
|
+
.kx-button-group > *:focus-visible, .kx-button-group > *:focus-within { z-index: 10; }
|
|
1264
|
+
.kx-button-group--horizontal { flex-direction: row; }
|
|
1265
|
+
/* -1px on the inline start of every child but the first: one shared border, not two stacked. */
|
|
1266
|
+
.kx-button-group--horizontal > *:not(:first-child) { margin-inline-start: -1px; border-start-start-radius: 0; border-end-start-radius: 0; }
|
|
1267
|
+
.kx-button-group--horizontal > *:not(:last-child) { border-start-end-radius: 0; border-end-end-radius: 0; }
|
|
1268
|
+
.kx-button-group--vertical { flex-direction: column; }
|
|
1269
|
+
.kx-button-group--vertical > *:not(:first-child) { margin-block-start: -1px; border-start-start-radius: 0; border-start-end-radius: 0; }
|
|
1270
|
+
.kx-button-group--vertical > *:not(:last-child) { border-end-start-radius: 0; border-end-end-radius: 0; }
|
|
1271
|
+
|
|
1272
|
+
.kx-button-group-separator { flex-shrink: 0; align-self: stretch; background-color: hsl(var(--border)); }
|
|
1273
|
+
.kx-button-group-separator--vertical { width: 1px; margin-block: var(--spacing-1); }
|
|
1274
|
+
.kx-button-group-separator--horizontal { height: 1px; margin-inline: var(--spacing-1); }
|
|
1275
|
+
|
|
1276
|
+
.kx-button-group-text {
|
|
1277
|
+
display: flex;
|
|
1278
|
+
align-items: center;
|
|
1279
|
+
gap: var(--spacing-1);
|
|
1280
|
+
border: var(--border-width-default) solid hsl(var(--input));
|
|
1281
|
+
border-radius: var(--radius-control);
|
|
1282
|
+
background-color: hsl(var(--muted));
|
|
1283
|
+
padding-inline: var(--spacing-3);
|
|
1284
|
+
color: hsl(var(--foreground));
|
|
1285
|
+
font: var(--text-label-md);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/* ── circular progress ──────────────────────────────────────────────────── */
|
|
1289
|
+
.kx-circular-progress { position: relative; display: inline-grid; place-items: center; font-family: var(--font-family-sans); }
|
|
1290
|
+
.kx-circular-progress svg { grid-area: 1 / 1; transform: rotate(-90deg); }
|
|
1291
|
+
.kx-circular-progress__track { stroke: hsl(var(--muted)); }
|
|
1292
|
+
.kx-circular-progress__indicator {
|
|
1293
|
+
stroke: hsl(var(--action));
|
|
1294
|
+
transition: stroke-dashoffset var(--duration-base) var(--easing-standard);
|
|
1295
|
+
}
|
|
1296
|
+
.kx-circular-progress__value { grid-area: 1 / 1; font: var(--text-label-sm); color: hsl(var(--foreground)); }
|
|
1297
|
+
/* No value means indeterminate: spin the quarter arc rather than show a bar stuck at zero. */
|
|
1298
|
+
.kx-circular-progress:not([aria-valuenow]) svg { animation: kx-spin 1s linear infinite; }
|
|
1299
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1300
|
+
.kx-circular-progress__indicator { transition: none; }
|
|
1301
|
+
/* 3s, as the spinner: it keeps turning so the page does not read as hung, but no faster than the 3s
|
|
1302
|
+
reduced-motion floor (check:angular-browser, "reduced motion") — it was 2.4s */
|
|
1303
|
+
.kx-circular-progress:not([aria-valuenow]) svg { animation-duration: 3s; }
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/* ── code block ─────────────────────────────────────────────────────────── */
|
|
1307
|
+
.kx-code-block {
|
|
1308
|
+
display: block;
|
|
1309
|
+
overflow: hidden;
|
|
1310
|
+
border: var(--border-width-default) solid hsl(var(--input));
|
|
1311
|
+
border-radius: var(--radius-md);
|
|
1312
|
+
background-color: hsl(var(--muted));
|
|
1313
|
+
font-family: var(--font-family-sans);
|
|
1314
|
+
}
|
|
1315
|
+
.kx-code-block__header {
|
|
1316
|
+
display: flex;
|
|
1317
|
+
align-items: center;
|
|
1318
|
+
justify-content: space-between;
|
|
1319
|
+
border-block-end: var(--border-width-default) solid hsl(var(--input));
|
|
1320
|
+
background-color: hsl(var(--background));
|
|
1321
|
+
padding-inline: var(--spacing-2);
|
|
1322
|
+
}
|
|
1323
|
+
.kx-code-block__tabs { display: flex; }
|
|
1324
|
+
.kx-code-block__tab {
|
|
1325
|
+
border: 0;
|
|
1326
|
+
border-block-end: 2px solid transparent;
|
|
1327
|
+
background: none;
|
|
1328
|
+
padding: var(--spacing-2) var(--spacing-3);
|
|
1329
|
+
color: hsl(var(--muted-foreground));
|
|
1330
|
+
font: var(--text-body-sm);
|
|
1331
|
+
cursor: pointer;
|
|
1332
|
+
}
|
|
1333
|
+
.kx-code-block__tab:hover { color: hsl(var(--foreground)); }
|
|
1334
|
+
.kx-code-block__tab--active { border-block-end-color: hsl(var(--action)); color: hsl(var(--foreground)); }
|
|
1335
|
+
.kx-code-block__tab:focus-visible { outline: var(--border-width-default) solid hsl(var(--ring)); outline-offset: -2px; }
|
|
1336
|
+
.kx-code-block__copy {
|
|
1337
|
+
border: 0;
|
|
1338
|
+
background: none;
|
|
1339
|
+
border-radius: 2px;
|
|
1340
|
+
padding: var(--spacing-1);
|
|
1341
|
+
color: hsl(var(--muted-foreground));
|
|
1342
|
+
cursor: pointer;
|
|
1343
|
+
}
|
|
1344
|
+
.kx-code-block__copy:hover { color: hsl(var(--foreground)); }
|
|
1345
|
+
.kx-code-block__copy:focus-visible { outline: var(--border-width-default) solid currentColor; }
|
|
1346
|
+
/* The default glyphs, and whatever an application projects in their place (`[kxDismissIcon]`,
|
|
1347
|
+
`[kxCopyIcon]` — an <svg>, or an icon component whose host wraps one). Sized here rather than on the SVG
|
|
1348
|
+
so a projected replacement gets the same box without carrying a size of its own, and so a consumer
|
|
1349
|
+
restyling the package can reach them. Until this rule named the projected slots, only the built-in glyph
|
|
1350
|
+
was sized: a projected <svg> with no width rendered at the browser's default 300×150. */
|
|
1351
|
+
.kx-dismiss-glyph,
|
|
1352
|
+
[kxDismissIcon],
|
|
1353
|
+
[kxDismissIcon] > svg { width: 0.875rem; height: 0.875rem; display: block; flex-shrink: 0; }
|
|
1354
|
+
.kx-copy-glyph,
|
|
1355
|
+
[kxCopyIcon],
|
|
1356
|
+
[kxCopyIcon] > svg { width: 1rem; height: 1rem; display: block; flex-shrink: 0; }
|
|
1357
|
+
|
|
1358
|
+
.kx-code-block__body { position: relative; }
|
|
1359
|
+
/* No filename and no tabs means no header to hold the copy button, so it sits over the code. */
|
|
1360
|
+
.kx-code-block__copy--floating { position: absolute; inset-block-start: var(--spacing-2); inset-inline-end: var(--spacing-2); }
|
|
1361
|
+
|
|
1362
|
+
.kx-code-block__pre {
|
|
1363
|
+
overflow-x: auto;
|
|
1364
|
+
margin: 0;
|
|
1365
|
+
padding: var(--spacing-4);
|
|
1366
|
+
font: var(--text-body-sm);
|
|
1367
|
+
font-family: var(--font-family-mono, ui-monospace, monospace);
|
|
1368
|
+
color: hsl(var(--foreground));
|
|
1369
|
+
}
|
|
1370
|
+
.kx-code-block__pre:focus-visible { outline: var(--border-width-default) solid hsl(var(--ring)); outline-offset: -2px; }
|
|
1371
|
+
|
|
1372
|
+
/* ── description list ───────────────────────────────────────────────────── */
|
|
1373
|
+
.kx-description-list {
|
|
1374
|
+
margin: 0;
|
|
1375
|
+
border: var(--border-width-default) solid hsl(var(--border));
|
|
1376
|
+
border-radius: var(--radius-lg);
|
|
1377
|
+
background-color: hsl(var(--muted) / 0.2);
|
|
1378
|
+
}
|
|
1379
|
+
.kx-description-list__item { display: flex; gap: var(--spacing-3); padding: var(--spacing-2) var(--spacing-3); }
|
|
1380
|
+
.kx-description-list__item:not(:first-child) { border-block-start: var(--border-width-default) solid hsl(var(--border)); }
|
|
1381
|
+
.kx-description-list__item--row { align-items: baseline; }
|
|
1382
|
+
.kx-description-list__item--stacked { flex-direction: column; }
|
|
1383
|
+
.kx-description-list__term {
|
|
1384
|
+
flex-shrink: 0;
|
|
1385
|
+
font-family: var(--font-family-mono, ui-monospace, monospace);
|
|
1386
|
+
font-size: 0.625rem;
|
|
1387
|
+
text-transform: uppercase;
|
|
1388
|
+
letter-spacing: 0.14em;
|
|
1389
|
+
color: hsl(var(--muted-foreground));
|
|
1390
|
+
}
|
|
1391
|
+
.kx-description-list__item--row .kx-description-list__term { width: 6rem; }
|
|
1392
|
+
.kx-description-list__value { margin: 0; min-width: 0; font: var(--text-body-sm); color: hsl(var(--foreground)); }
|
|
1393
|
+
|
|
1394
|
+
/* ── fab ────────────────────────────────────────────────────────────────── */
|
|
1395
|
+
.kx-fab {
|
|
1396
|
+
display: inline-flex;
|
|
1397
|
+
flex-shrink: 0;
|
|
1398
|
+
align-items: center;
|
|
1399
|
+
justify-content: center;
|
|
1400
|
+
gap: var(--spacing-2);
|
|
1401
|
+
border: 0;
|
|
1402
|
+
border-radius: var(--radius-full);
|
|
1403
|
+
box-shadow: var(--shadow-lg);
|
|
1404
|
+
font-family: var(--font-family-sans);
|
|
1405
|
+
font-weight: var(--font-weight-medium);
|
|
1406
|
+
cursor: pointer;
|
|
1407
|
+
transition: background-color var(--duration-fast) var(--easing-standard);
|
|
1408
|
+
}
|
|
1409
|
+
.kx-fab:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1410
|
+
.kx-fab:disabled { pointer-events: none; opacity: var(--opacity-disabled); }
|
|
1411
|
+
.kx-fab--default { width: 3.5rem; height: 3.5rem; }
|
|
1412
|
+
.kx-fab--sm { width: 2.75rem; height: 2.75rem; }
|
|
1413
|
+
.kx-fab--extended { width: auto; padding-inline: var(--spacing-5); }
|
|
1414
|
+
.kx-fab--Primary { background-color: hsl(var(--action)); color: hsl(var(--action-foreground)); }
|
|
1415
|
+
.kx-fab--Primary:hover { background-color: hsl(var(--action) / 0.9); }
|
|
1416
|
+
.kx-fab--Secondary { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); }
|
|
1417
|
+
.kx-fab--Secondary:hover { background-color: hsl(var(--secondary) / 0.85); }
|
|
1418
|
+
@media (prefers-reduced-motion: reduce) { .kx-fab { transition: none; } }
|
|
1419
|
+
|
|
1420
|
+
/* ── image ──────────────────────────────────────────────────────────────── */
|
|
1421
|
+
.kx-image { position: relative; display: block; inline-size: 100%; overflow: hidden; background-color: hsl(var(--muted)); aspect-ratio: var(--kx-image-ratio, 1); }
|
|
1422
|
+
.kx-image--rounded { border-radius: var(--radius-md); }
|
|
1423
|
+
.kx-image__img {
|
|
1424
|
+
width: 100%;
|
|
1425
|
+
height: 100%;
|
|
1426
|
+
object-fit: cover;
|
|
1427
|
+
opacity: var(--opacity-hidden);
|
|
1428
|
+
transition: opacity var(--duration-base) var(--easing-standard);
|
|
1429
|
+
}
|
|
1430
|
+
.kx-image__img--loaded { opacity: var(--opacity-visible); }
|
|
1431
|
+
.kx-image__fallback { position: absolute; inset: 0; display: grid; place-items: center; color: hsl(var(--muted-foreground)); }
|
|
1432
|
+
@media (prefers-reduced-motion: reduce) { .kx-image__img { transition: none; } }
|
|
1433
|
+
|
|
1434
|
+
/* ── list ───────────────────────────────────────────────────────────────── */
|
|
1435
|
+
.kx-list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
|
|
1436
|
+
.kx-list__item { display: block; }
|
|
1437
|
+
.kx-list__item:not(:first-child) { border-block-start: var(--border-width-default) solid hsl(var(--border)); }
|
|
1438
|
+
.kx-list__row {
|
|
1439
|
+
display: flex;
|
|
1440
|
+
align-items: center;
|
|
1441
|
+
gap: var(--spacing-3);
|
|
1442
|
+
width: 100%;
|
|
1443
|
+
padding: var(--spacing-3);
|
|
1444
|
+
font-family: var(--font-family-sans);
|
|
1445
|
+
text-align: start;
|
|
1446
|
+
}
|
|
1447
|
+
.kx-list__row--pressable {
|
|
1448
|
+
border: 0;
|
|
1449
|
+
background: none;
|
|
1450
|
+
color: inherit;
|
|
1451
|
+
font: inherit;
|
|
1452
|
+
cursor: pointer;
|
|
1453
|
+
}
|
|
1454
|
+
/* The ring, not only the fill: the accent background is under 3:1 against the surface in dark mode, so
|
|
1455
|
+
hover and focus must not rely on it alone to be perceivable. */
|
|
1456
|
+
.kx-list__row--pressable:hover,
|
|
1457
|
+
.kx-list__row--pressable:focus-visible {
|
|
1458
|
+
background-color: hsl(var(--accent));
|
|
1459
|
+
outline: var(--border-width-default) solid hsl(var(--ring));
|
|
1460
|
+
outline-offset: -1px;
|
|
1461
|
+
}
|
|
1462
|
+
.kx-list__row--pressable:disabled { pointer-events: none; }
|
|
1463
|
+
.kx-list__item--disabled { opacity: var(--opacity-disabled); }
|
|
1464
|
+
.kx-list__leading:empty, .kx-list__trailing:empty { display: none; }
|
|
1465
|
+
.kx-list__leading, .kx-list__trailing { display: flex; flex-shrink: 0; align-items: center; gap: var(--spacing-2); }
|
|
1466
|
+
.kx-list__text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 0%; }
|
|
1467
|
+
.kx-list__title { font: var(--text-body-md); color: hsl(var(--foreground)); }
|
|
1468
|
+
.kx-list__description { font: var(--text-body-sm); color: hsl(var(--muted-foreground)); }
|
|
1469
|
+
|
|
1470
|
+
/* ── marquee ────────────────────────────────────────────────────────────── */
|
|
1471
|
+
.kx-marquee { display: flex; overflow: hidden; width: 100%; }
|
|
1472
|
+
.kx-marquee__track { display: flex; flex-shrink: 0; animation: kx-marquee-scroll linear infinite; }
|
|
1473
|
+
.kx-marquee__group { display: flex; flex-shrink: 0; align-items: center; gap: var(--spacing-4); padding-inline-end: var(--spacing-4); }
|
|
1474
|
+
.kx-marquee--pause-on-hover:hover .kx-marquee__track { animation-play-state: paused; }
|
|
1475
|
+
@keyframes kx-marquee-scroll {
|
|
1476
|
+
from { transform: translateX(0); }
|
|
1477
|
+
to { transform: translateX(-50%); }
|
|
1478
|
+
}
|
|
1479
|
+
/* RTL: the same keyframes would scroll the wrong way, so the whole track is mirrored. */
|
|
1480
|
+
[dir='rtl'] .kx-marquee__track { animation-name: kx-marquee-scroll-rtl; }
|
|
1481
|
+
@keyframes kx-marquee-scroll-rtl {
|
|
1482
|
+
from { transform: translateX(0); }
|
|
1483
|
+
to { transform: translateX(50%); }
|
|
1484
|
+
}
|
|
1485
|
+
/* Continuous motion is WCAG 2.2.2's subject. With reduce, the animation stops and the strip stays
|
|
1486
|
+
scrollable, so nothing becomes unreachable. */
|
|
1487
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1488
|
+
.kx-marquee { overflow-x: auto; }
|
|
1489
|
+
.kx-marquee__track { animation: none; }
|
|
1490
|
+
.kx-marquee__group[aria-hidden='true'] { display: none; }
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/* ── page header ────────────────────────────────────────────────────────── */
|
|
1494
|
+
.kx-page-header {
|
|
1495
|
+
display: flex;
|
|
1496
|
+
flex-direction: column;
|
|
1497
|
+
gap: var(--spacing-4);
|
|
1498
|
+
border-block-end: var(--border-width-default) solid hsl(var(--border));
|
|
1499
|
+
padding-block-end: var(--spacing-6);
|
|
1500
|
+
font-family: var(--font-family-sans);
|
|
1501
|
+
}
|
|
1502
|
+
.kx-page-header__breadcrumb:empty { display: none; }
|
|
1503
|
+
.kx-page-header__breadcrumb { font: var(--text-body-sm); }
|
|
1504
|
+
.kx-page-header__main { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: var(--spacing-4); }
|
|
1505
|
+
.kx-page-header__titles { display: flex; flex-direction: column; gap: var(--spacing-1); min-width: 0; }
|
|
1506
|
+
.kx-page-header__title { font: var(--text-headline-sm); font-weight: var(--font-weight-medium); color: hsl(var(--foreground)); }
|
|
1507
|
+
.kx-page-header__description { margin: 0; font: var(--text-body-md); color: hsl(var(--muted-foreground)); }
|
|
1508
|
+
.kx-page-header__actions:empty { display: none; }
|
|
1509
|
+
.kx-page-header__actions { display: flex; flex-shrink: 0; flex-wrap: wrap; align-items: center; gap: var(--spacing-2); }
|
|
1510
|
+
|
|
1511
|
+
/* ── timeline ───────────────────────────────────────────────────────────── */
|
|
1512
|
+
.kx-timeline { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
|
|
1513
|
+
.kx-timeline__item { position: relative; display: flex; gap: var(--spacing-3); padding-block-end: var(--spacing-5); }
|
|
1514
|
+
/* The connecting line sits on the inline start edge, so it mirrors with the marker under dir="rtl". */
|
|
1515
|
+
.kx-timeline__item:not(:last-child)::before {
|
|
1516
|
+
content: '';
|
|
1517
|
+
position: absolute;
|
|
1518
|
+
inset-block: 1.25rem auto;
|
|
1519
|
+
height: calc(100% - 1.25rem);
|
|
1520
|
+
inset-inline-start: 0.4375rem;
|
|
1521
|
+
width: 1px;
|
|
1522
|
+
background-color: hsl(var(--border));
|
|
1523
|
+
}
|
|
1524
|
+
.kx-timeline__marker {
|
|
1525
|
+
position: relative;
|
|
1526
|
+
z-index: 1;
|
|
1527
|
+
flex-shrink: 0;
|
|
1528
|
+
display: grid;
|
|
1529
|
+
place-items: center;
|
|
1530
|
+
width: 0.875rem;
|
|
1531
|
+
height: 0.875rem;
|
|
1532
|
+
margin-block-start: 0.375rem;
|
|
1533
|
+
border-radius: var(--radius-full);
|
|
1534
|
+
border: 2px solid hsl(var(--background));
|
|
1535
|
+
box-sizing: content-box;
|
|
1536
|
+
}
|
|
1537
|
+
.kx-timeline__marker--default { background-color: hsl(var(--muted-foreground)); }
|
|
1538
|
+
.kx-timeline__marker--active { background-color: hsl(var(--action)); }
|
|
1539
|
+
.kx-timeline__marker--success { background-color: hsl(var(--success)); }
|
|
1540
|
+
.kx-timeline__marker--error { background-color: hsl(var(--destructive)); }
|
|
1541
|
+
.kx-timeline__content { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
1542
|
+
.kx-timeline__title { font: var(--text-body-md); color: hsl(var(--foreground)); }
|
|
1543
|
+
.kx-timeline__description { font: var(--text-body-sm); color: hsl(var(--muted-foreground)); }
|
|
1544
|
+
|
|
1545
|
+
/* ══ navigation and disclosure ═════════════════════════════════════════════
|
|
1546
|
+
accordion · collapsible · breadcrumb · pagination · table of contents
|
|
1547
|
+
tab bar · stepper · navigation bar · app bar · footer
|
|
1548
|
+
|
|
1549
|
+
One state contract for every destination (TOKENS.md, "Navigation and disclosure"):
|
|
1550
|
+
rest muted text, no surface — navigation recedes behind content
|
|
1551
|
+
hover `--foreground` text on the 8% `--foreground` layer the tabs and selection controls use
|
|
1552
|
+
pressed `:active`, the 12% layer: deeper than hover, so a press is seen before the page changes
|
|
1553
|
+
current `--foreground` text AND a shape cue (an indicator bar, an outlined raised surface, or a pill)
|
|
1554
|
+
— never hue alone
|
|
1555
|
+
focus the shared `--shadow-focus` ring, which no other state draws, so focus out-draws hover
|
|
1556
|
+
disabled `--opacity-disabled`, and no answer to the pointer
|
|
1557
|
+
Colour, background and shadow transition over `--duration-fast`; nothing moves or resizes, and under
|
|
1558
|
+
reduced motion nothing transitions (below). Logical properties throughout, so every row and indent
|
|
1559
|
+
mirrors with the direction its own element resolves to.
|
|
1560
|
+
════════════════════════════════════════════════════════════════════════ */
|
|
1561
|
+
|
|
1562
|
+
/* ── disclosure: the shared expand / collapse ───────────────────────────── */
|
|
1563
|
+
/* A one-row grid whose track goes 0fr → 1fr: the block size interpolates to the content's natural height
|
|
1564
|
+
with no measured height variable (disclosure.ts, "Motion"). Visibility switches on at the start of an
|
|
1565
|
+
expansion and off at the end of a collapse, so closed content is out of the tab order and the
|
|
1566
|
+
accessibility tree, but is seen shrinking rather than vanishing. */
|
|
1567
|
+
.kx-disclosure {
|
|
1568
|
+
display: grid;
|
|
1569
|
+
grid-template-rows: 0fr;
|
|
1570
|
+
visibility: hidden;
|
|
1571
|
+
transition:
|
|
1572
|
+
grid-template-rows var(--duration-fast) var(--easing-exit),
|
|
1573
|
+
visibility 0s linear var(--duration-fast);
|
|
1574
|
+
}
|
|
1575
|
+
.kx-disclosure[data-state='open'] {
|
|
1576
|
+
grid-template-rows: 1fr;
|
|
1577
|
+
visibility: visible;
|
|
1578
|
+
transition:
|
|
1579
|
+
grid-template-rows var(--duration-fast) var(--easing-enter),
|
|
1580
|
+
visibility 0s linear 0s;
|
|
1581
|
+
}
|
|
1582
|
+
/* clip, not hidden: the height must clip while it animates, but a focus ring on content flush with the edge
|
|
1583
|
+
(4px: --shadow-focus's outer spread) must not be cut */
|
|
1584
|
+
.kx-disclosure__body { min-block-size: 0; overflow: clip; overflow-clip-margin: 4px; }
|
|
1585
|
+
|
|
1586
|
+
/* ── accordion ──────────────────────────────────────────────────────────── */
|
|
1587
|
+
/* Items are divided, not boxed: one rule between them, no border around the whole, no shadow. */
|
|
1588
|
+
.kx-accordion { display: block; font-family: var(--font-family-sans); }
|
|
1589
|
+
.kx-accordion__item { display: block; border-block-end: var(--border-width-default) solid hsl(var(--border)); }
|
|
1590
|
+
.kx-accordion__heading { display: flex; margin: 0; }
|
|
1591
|
+
.kx-accordion__trigger {
|
|
1592
|
+
display: flex;
|
|
1593
|
+
flex: 1 1 auto;
|
|
1594
|
+
align-items: center;
|
|
1595
|
+
justify-content: space-between;
|
|
1596
|
+
gap: var(--spacing-3);
|
|
1597
|
+
min-block-size: 2.75rem;
|
|
1598
|
+
margin: 0;
|
|
1599
|
+
padding: var(--spacing-3) var(--spacing-2);
|
|
1600
|
+
border: 0;
|
|
1601
|
+
border-radius: var(--radius-sm);
|
|
1602
|
+
background: transparent;
|
|
1603
|
+
color: hsl(var(--foreground));
|
|
1604
|
+
font: var(--text-label-lg);
|
|
1605
|
+
font-weight: var(--font-weight-medium);
|
|
1606
|
+
text-align: start;
|
|
1607
|
+
cursor: pointer;
|
|
1608
|
+
transition:
|
|
1609
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
1610
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
1611
|
+
}
|
|
1612
|
+
.kx-accordion__label { min-inline-size: 0; overflow-wrap: break-word; }
|
|
1613
|
+
@media (hover: hover) {
|
|
1614
|
+
.kx-accordion__trigger:enabled:not([aria-disabled='true']):hover { background-color: hsl(var(--foreground) / 0.08); }
|
|
1615
|
+
}
|
|
1616
|
+
.kx-accordion__trigger:enabled:not([aria-disabled='true']):active { background-color: hsl(var(--foreground) / 0.12); }
|
|
1617
|
+
.kx-accordion__trigger:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1618
|
+
.kx-accordion__trigger[aria-disabled='true'] { cursor: default; }
|
|
1619
|
+
.kx-accordion__trigger:disabled { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
1620
|
+
.kx-accordion__chevron {
|
|
1621
|
+
flex-shrink: 0;
|
|
1622
|
+
inline-size: 1rem;
|
|
1623
|
+
block-size: 1rem;
|
|
1624
|
+
color: hsl(var(--muted-foreground));
|
|
1625
|
+
transition: transform var(--duration-fast) var(--easing-standard);
|
|
1626
|
+
}
|
|
1627
|
+
.kx-accordion__trigger[aria-expanded='true'] .kx-accordion__chevron { transform: rotate(180deg); color: hsl(var(--foreground)); }
|
|
1628
|
+
.kx-accordion__inner {
|
|
1629
|
+
padding: 0 var(--spacing-2) var(--spacing-4);
|
|
1630
|
+
font: var(--text-body-md);
|
|
1631
|
+
color: hsl(var(--muted-foreground));
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/* ── collapsible ────────────────────────────────────────────────────────── */
|
|
1635
|
+
.kx-collapsible { display: block; }
|
|
1636
|
+
.kx-collapsible__content > .kx-disclosure__body > :first-child { margin-block-start: var(--spacing-2); }
|
|
1637
|
+
|
|
1638
|
+
/* ── breadcrumb ─────────────────────────────────────────────────────────── */
|
|
1639
|
+
.kx-breadcrumb { font-family: var(--font-family-sans); }
|
|
1640
|
+
.kx-breadcrumb__list {
|
|
1641
|
+
display: flex;
|
|
1642
|
+
flex-wrap: wrap;
|
|
1643
|
+
align-items: center;
|
|
1644
|
+
gap: var(--spacing-1) var(--spacing-2);
|
|
1645
|
+
margin: 0;
|
|
1646
|
+
padding: 0;
|
|
1647
|
+
list-style: none;
|
|
1648
|
+
font: var(--text-body-sm);
|
|
1649
|
+
color: hsl(var(--muted-foreground));
|
|
1650
|
+
}
|
|
1651
|
+
.kx-breadcrumb__item { display: inline-flex; align-items: center; min-inline-size: 0; overflow-wrap: break-word; }
|
|
1652
|
+
.kx-breadcrumb__link {
|
|
1653
|
+
border-radius: var(--radius-sm);
|
|
1654
|
+
color: hsl(var(--muted-foreground));
|
|
1655
|
+
text-decoration: underline;
|
|
1656
|
+
text-decoration-color: transparent;
|
|
1657
|
+
text-underline-offset: 3px;
|
|
1658
|
+
transition:
|
|
1659
|
+
color var(--duration-fast) var(--easing-standard),
|
|
1660
|
+
text-decoration-color var(--duration-fast) var(--easing-standard),
|
|
1661
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
1662
|
+
}
|
|
1663
|
+
@media (hover: hover) {
|
|
1664
|
+
.kx-breadcrumb__link:hover { color: hsl(var(--foreground)); text-decoration-color: currentColor; }
|
|
1665
|
+
}
|
|
1666
|
+
.kx-breadcrumb__link:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1667
|
+
/* current: full-strength text AND weight — the one crumb that is not a link reads as where you are */
|
|
1668
|
+
.kx-breadcrumb__page { color: hsl(var(--foreground)); font-weight: var(--font-weight-medium); }
|
|
1669
|
+
.kx-breadcrumb__separator { display: inline-flex; font-size: 1.25em; line-height: 1; color: hsl(var(--muted-foreground)); }
|
|
1670
|
+
|
|
1671
|
+
/* ── shared destination: pagination, app bar ────────────────────────────── */
|
|
1672
|
+
.kx-pagination__link,
|
|
1673
|
+
.kx-app-bar__link {
|
|
1674
|
+
display: inline-flex;
|
|
1675
|
+
align-items: center;
|
|
1676
|
+
justify-content: center;
|
|
1677
|
+
gap: var(--spacing-1);
|
|
1678
|
+
min-block-size: 2.25rem;
|
|
1679
|
+
margin: 0;
|
|
1680
|
+
border: var(--border-width-default) solid transparent;
|
|
1681
|
+
border-radius: var(--radius-control);
|
|
1682
|
+
background: transparent;
|
|
1683
|
+
color: hsl(var(--muted-foreground));
|
|
1684
|
+
font: var(--text-label-lg);
|
|
1685
|
+
font-weight: var(--font-weight-medium);
|
|
1686
|
+
text-decoration: none;
|
|
1687
|
+
white-space: nowrap;
|
|
1688
|
+
cursor: pointer;
|
|
1689
|
+
transition:
|
|
1690
|
+
color var(--duration-fast) var(--easing-standard),
|
|
1691
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
1692
|
+
border-color var(--duration-fast) var(--easing-standard),
|
|
1693
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
1694
|
+
}
|
|
1695
|
+
@media (hover: hover) {
|
|
1696
|
+
:is(.kx-pagination__link, .kx-app-bar__link):not(:disabled, [aria-disabled='true']):hover {
|
|
1697
|
+
color: hsl(var(--foreground));
|
|
1698
|
+
background-color: hsl(var(--foreground) / 0.08);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
:is(.kx-pagination__link, .kx-app-bar__link):not(:disabled, [aria-disabled='true']):active { background-color: hsl(var(--foreground) / 0.12); }
|
|
1702
|
+
:is(.kx-pagination__link, .kx-app-bar__link):focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1703
|
+
:is(.kx-pagination__link, .kx-app-bar__link):is(:disabled, [aria-disabled='true']) { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
1704
|
+
|
|
1705
|
+
/* ── pagination ─────────────────────────────────────────────────────────── */
|
|
1706
|
+
.kx-pagination { display: flex; justify-content: center; inline-size: 100%; font-family: var(--font-family-sans); }
|
|
1707
|
+
.kx-pagination__content { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--spacing-1); margin: 0; padding: 0; list-style: none; }
|
|
1708
|
+
.kx-pagination__item { display: flex; }
|
|
1709
|
+
.kx-pagination__link { min-inline-size: 2.25rem; padding-inline: var(--spacing-2); }
|
|
1710
|
+
.kx-pagination__step { padding-inline: var(--spacing-3); }
|
|
1711
|
+
.kx-pagination__glyph { font-size: 1.25em; line-height: 1; }
|
|
1712
|
+
/* current: an outlined surface — an edge at 3:1 (the field edge, `--muted-foreground` at 80%), a fill and a
|
|
1713
|
+
small lift; not a colour. `--input` (2.2:1 light, 2.7:1 dark) measured under SC 1.4.11's 3:1. */
|
|
1714
|
+
.kx-pagination__link[aria-current='page'] {
|
|
1715
|
+
border-color: hsl(var(--muted-foreground) / 0.8);
|
|
1716
|
+
background-color: hsl(var(--background));
|
|
1717
|
+
color: hsl(var(--foreground));
|
|
1718
|
+
box-shadow: var(--shadow-sm);
|
|
1719
|
+
}
|
|
1720
|
+
.kx-pagination__ellipsis { display: inline-flex; align-items: center; justify-content: center; min-inline-size: 2.25rem; min-block-size: 2.25rem; color: hsl(var(--muted-foreground)); }
|
|
1721
|
+
|
|
1722
|
+
/* ── table of contents ──────────────────────────────────────────────────── */
|
|
1723
|
+
.kx-toc { font-family: var(--font-family-sans); }
|
|
1724
|
+
.kx-toc__list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; border-inline-start: var(--border-width-default) solid hsl(var(--border)); }
|
|
1725
|
+
.kx-toc__link {
|
|
1726
|
+
display: block;
|
|
1727
|
+
margin-inline-start: -1px;
|
|
1728
|
+
padding-block: var(--spacing-1);
|
|
1729
|
+
/* each level indents 0.75rem further from the inline start; rem, so the indent grows with the text */
|
|
1730
|
+
padding-inline: calc(0.75rem + var(--kx-toc-level, 0) * 0.75rem) var(--spacing-2);
|
|
1731
|
+
border-inline-start: var(--border-width-focus) solid transparent;
|
|
1732
|
+
border-start-end-radius: var(--radius-sm);
|
|
1733
|
+
border-end-end-radius: var(--radius-sm);
|
|
1734
|
+
font: var(--text-body-sm);
|
|
1735
|
+
color: hsl(var(--muted-foreground));
|
|
1736
|
+
text-decoration: none;
|
|
1737
|
+
overflow-wrap: break-word;
|
|
1738
|
+
transition:
|
|
1739
|
+
color var(--duration-fast) var(--easing-standard),
|
|
1740
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
1741
|
+
border-color var(--duration-fast) var(--easing-standard),
|
|
1742
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
1743
|
+
}
|
|
1744
|
+
@media (hover: hover) {
|
|
1745
|
+
.kx-toc__link:not([aria-current]):hover { color: hsl(var(--foreground)); background-color: hsl(var(--foreground) / 0.08); }
|
|
1746
|
+
}
|
|
1747
|
+
.kx-toc__link:not([aria-current]):active { background-color: hsl(var(--foreground) / 0.12); }
|
|
1748
|
+
.kx-toc__link:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1749
|
+
/* current: the 2px `--primary` bar on the inline-start edge, foreground text, medium weight */
|
|
1750
|
+
.kx-toc__link[aria-current] { border-inline-start-color: hsl(var(--primary)); color: hsl(var(--foreground)); font-weight: var(--font-weight-medium); }
|
|
1751
|
+
|
|
1752
|
+
/* ── tab bar ────────────────────────────────────────────────────────────── */
|
|
1753
|
+
.kx-tab-bar {
|
|
1754
|
+
display: flex;
|
|
1755
|
+
align-items: stretch;
|
|
1756
|
+
inline-size: 100%;
|
|
1757
|
+
border-block-start: var(--border-width-default) solid hsl(var(--border));
|
|
1758
|
+
background-color: hsl(var(--background));
|
|
1759
|
+
font-family: var(--font-family-sans);
|
|
1760
|
+
}
|
|
1761
|
+
.kx-tab-bar__item {
|
|
1762
|
+
display: flex;
|
|
1763
|
+
flex: 1 1 0%;
|
|
1764
|
+
min-inline-size: 0;
|
|
1765
|
+
flex-direction: column;
|
|
1766
|
+
align-items: center;
|
|
1767
|
+
justify-content: center;
|
|
1768
|
+
gap: var(--spacing-1);
|
|
1769
|
+
margin: 0;
|
|
1770
|
+
padding: var(--spacing-2) var(--spacing-1);
|
|
1771
|
+
border: 0;
|
|
1772
|
+
border-radius: var(--radius-control);
|
|
1773
|
+
background: transparent;
|
|
1774
|
+
color: hsl(var(--muted-foreground));
|
|
1775
|
+
font: var(--text-label-md);
|
|
1776
|
+
font-weight: var(--font-weight-medium);
|
|
1777
|
+
text-align: center;
|
|
1778
|
+
text-decoration: none;
|
|
1779
|
+
cursor: pointer;
|
|
1780
|
+
transition:
|
|
1781
|
+
color var(--duration-fast) var(--easing-standard),
|
|
1782
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
1783
|
+
}
|
|
1784
|
+
.kx-tab-bar__icon {
|
|
1785
|
+
position: relative;
|
|
1786
|
+
display: inline-flex;
|
|
1787
|
+
align-items: center;
|
|
1788
|
+
justify-content: center;
|
|
1789
|
+
inline-size: 4rem;
|
|
1790
|
+
max-inline-size: 100%;
|
|
1791
|
+
block-size: 2rem;
|
|
1792
|
+
border-radius: var(--radius-full);
|
|
1793
|
+
transition: background-color var(--duration-fast) var(--easing-standard);
|
|
1794
|
+
}
|
|
1795
|
+
.kx-tab-bar__icon > :not(.kx-tab-bar__badge) { inline-size: 1.5rem; block-size: 1.5rem; }
|
|
1796
|
+
.kx-tab-bar__label { max-inline-size: 100%; overflow-wrap: break-word; }
|
|
1797
|
+
.kx-tab-bar__badge {
|
|
1798
|
+
position: absolute;
|
|
1799
|
+
inset-block-start: 0;
|
|
1800
|
+
inset-inline-end: 0.75rem;
|
|
1801
|
+
display: inline-flex;
|
|
1802
|
+
align-items: center;
|
|
1803
|
+
justify-content: center;
|
|
1804
|
+
min-inline-size: 1.125rem;
|
|
1805
|
+
block-size: 1.125rem;
|
|
1806
|
+
padding-inline: 0.25rem;
|
|
1807
|
+
border-radius: var(--radius-full);
|
|
1808
|
+
background-color: hsl(var(--destructive));
|
|
1809
|
+
color: hsl(var(--destructive-foreground));
|
|
1810
|
+
font: var(--text-label-sm);
|
|
1811
|
+
font-weight: var(--font-weight-medium);
|
|
1812
|
+
}
|
|
1813
|
+
@media (hover: hover) {
|
|
1814
|
+
.kx-tab-bar__item:not([aria-current], :disabled, [aria-disabled='true']):hover { color: hsl(var(--foreground)); }
|
|
1815
|
+
.kx-tab-bar__item:not([aria-current], :disabled, [aria-disabled='true']):hover .kx-tab-bar__icon { background-color: hsl(var(--foreground) / 0.08); }
|
|
1816
|
+
}
|
|
1817
|
+
.kx-tab-bar__item:not(:disabled, [aria-disabled='true']):active .kx-tab-bar__icon { background-color: hsl(var(--foreground) / 0.12); }
|
|
1818
|
+
.kx-tab-bar__item:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1819
|
+
/* current: full-strength label, an action-coloured icon AND a pill behind it. The label is `--foreground`, not
|
|
1820
|
+
`--action`: in dark, `--action` text (7.9:1) is weaker than the resting `--muted-foreground` (9.0:1), so the
|
|
1821
|
+
current destination would read quieter than the others. */
|
|
1822
|
+
.kx-tab-bar__item[aria-current='page'] { color: hsl(var(--foreground)); }
|
|
1823
|
+
.kx-tab-bar__item[aria-current='page'] .kx-tab-bar__icon { color: hsl(var(--action)); background-color: hsl(var(--action) / 0.14); }
|
|
1824
|
+
.kx-tab-bar__item:is(:disabled, [aria-disabled='true']) { opacity: var(--opacity-disabled); cursor: not-allowed; }
|
|
1825
|
+
|
|
1826
|
+
/* ── stepper ────────────────────────────────────────────────────────────── */
|
|
1827
|
+
.kx-stepper { display: flex; margin: 0; padding: 0; list-style: none; font-family: var(--font-family-sans); }
|
|
1828
|
+
.kx-stepper--horizontal { flex-wrap: wrap; align-items: flex-start; inline-size: 100%; }
|
|
1829
|
+
.kx-stepper--vertical { flex-direction: column; }
|
|
1830
|
+
.kx-stepper__step { display: flex; min-inline-size: 0; }
|
|
1831
|
+
.kx-stepper--horizontal .kx-stepper__step { flex: 1 1 0%; flex-direction: column; align-items: center; gap: var(--spacing-2); text-align: center; }
|
|
1832
|
+
.kx-stepper--vertical .kx-stepper__step { gap: var(--spacing-3); }
|
|
1833
|
+
.kx-stepper__track { display: flex; align-items: center; }
|
|
1834
|
+
.kx-stepper--horizontal .kx-stepper__track { inline-size: 100%; justify-content: center; position: relative; }
|
|
1835
|
+
.kx-stepper--vertical .kx-stepper__track { flex-direction: column; align-self: stretch; }
|
|
1836
|
+
.kx-stepper__marker {
|
|
1837
|
+
position: relative;
|
|
1838
|
+
z-index: 1;
|
|
1839
|
+
display: inline-flex;
|
|
1840
|
+
flex-shrink: 0;
|
|
1841
|
+
align-items: center;
|
|
1842
|
+
justify-content: center;
|
|
1843
|
+
inline-size: 1.75rem;
|
|
1844
|
+
block-size: 1.75rem;
|
|
1845
|
+
border: var(--border-width-default) solid hsl(var(--input));
|
|
1846
|
+
border-radius: var(--radius-full);
|
|
1847
|
+
background-color: hsl(var(--background));
|
|
1848
|
+
color: hsl(var(--muted-foreground));
|
|
1849
|
+
font: var(--text-label-md);
|
|
1850
|
+
font-weight: var(--font-weight-medium);
|
|
1851
|
+
}
|
|
1852
|
+
.kx-stepper__marker > svg { inline-size: 1rem; block-size: 1rem; }
|
|
1853
|
+
/* complete: a filled action disc with a check; current: a 2px action ring around the number */
|
|
1854
|
+
.kx-stepper__step[data-status='complete'] .kx-stepper__marker { border-color: hsl(var(--action)); background-color: hsl(var(--action)); color: hsl(var(--action-foreground)); }
|
|
1855
|
+
.kx-stepper__step[data-status='current'] .kx-stepper__marker { border: var(--border-width-focus) solid hsl(var(--action)); color: hsl(var(--action)); }
|
|
1856
|
+
/* the connector runs from this marker to the next one, on the inline axis or the block axis */
|
|
1857
|
+
.kx-stepper__connector { background-color: hsl(var(--border)); }
|
|
1858
|
+
.kx-stepper__step[data-status='complete'] .kx-stepper__connector { background-color: hsl(var(--action)); }
|
|
1859
|
+
/* Horizontal, each step draws its own halves of the line — marker to its inline-end edge, and (after the first)
|
|
1860
|
+
its inline-start edge to the marker — so nothing reaches into a neighbour's box, and a step's box holds
|
|
1861
|
+
everything it paints, at any text size. The leading half is coloured once this step is reached. */
|
|
1862
|
+
.kx-stepper--horizontal .kx-stepper__connector { position: absolute; inset-inline-start: calc(50% + 0.875rem + var(--spacing-1)); inset-inline-end: 0; inset-block-start: 50%; block-size: 1px; }
|
|
1863
|
+
.kx-stepper--horizontal .kx-stepper__step:not(:first-child) .kx-stepper__track::before {
|
|
1864
|
+
content: '';
|
|
1865
|
+
position: absolute;
|
|
1866
|
+
inset-inline-start: 0;
|
|
1867
|
+
inset-inline-end: calc(50% + 0.875rem + var(--spacing-1));
|
|
1868
|
+
inset-block-start: 50%;
|
|
1869
|
+
block-size: 1px;
|
|
1870
|
+
background-color: hsl(var(--border));
|
|
1871
|
+
}
|
|
1872
|
+
.kx-stepper--horizontal .kx-stepper__step:is([data-status='complete'], [data-status='current']):not(:first-child) .kx-stepper__track::before { background-color: hsl(var(--action)); }
|
|
1873
|
+
.kx-stepper--vertical .kx-stepper__connector { flex: 1 1 auto; inline-size: 1px; min-block-size: var(--spacing-6); margin-block: var(--spacing-1); }
|
|
1874
|
+
.kx-stepper__text { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; overflow-wrap: break-word; }
|
|
1875
|
+
.kx-stepper--horizontal .kx-stepper__text { padding-inline: var(--spacing-1); }
|
|
1876
|
+
.kx-stepper--vertical .kx-stepper__text { padding-block-end: var(--spacing-6); }
|
|
1877
|
+
.kx-stepper__label { font: var(--text-label-lg); font-weight: var(--font-weight-medium); color: hsl(var(--foreground)); }
|
|
1878
|
+
.kx-stepper__step[data-status='upcoming'] .kx-stepper__label { color: hsl(var(--muted-foreground)); }
|
|
1879
|
+
.kx-stepper__description { font: var(--text-body-sm); color: hsl(var(--muted-foreground)); }
|
|
1880
|
+
/* A horizontal stepper reflows into a list when its own width is under 20em of its own text — a phone, or any
|
|
1881
|
+
width once the reader doubles the text size — instead of breaking its labels mid-word. em, not px, so the
|
|
1882
|
+
switch follows the text size; a container query, so it follows the space the stepper is given. The
|
|
1883
|
+
connectors go: list order and the markers carry the sequence. */
|
|
1884
|
+
.kx-stepper--horizontal { container-type: inline-size; }
|
|
1885
|
+
@container (max-width: 20em) {
|
|
1886
|
+
.kx-stepper--horizontal .kx-stepper__step { flex: 1 1 100%; flex-direction: row; align-items: flex-start; gap: var(--spacing-3); text-align: start; padding-block-end: var(--spacing-3); }
|
|
1887
|
+
.kx-stepper--horizontal .kx-stepper__track { inline-size: auto; }
|
|
1888
|
+
.kx-stepper--horizontal .kx-stepper__connector,
|
|
1889
|
+
.kx-stepper--horizontal .kx-stepper__track::before { display: none; }
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
/* ── navigation bar ─────────────────────────────────────────────────────── */
|
|
1893
|
+
/* Wraps rather than squeezing: when the actions leave the titles less than about five characters (two text
|
|
1894
|
+
actions at 200% text on a phone), the actions move to a second row at the inline end instead of the title
|
|
1895
|
+
being broken one letter per line (found composing it with a sheet and a drawer, Wave C1). */
|
|
1896
|
+
.kx-navigation-bar {
|
|
1897
|
+
display: flex;
|
|
1898
|
+
flex-wrap: wrap;
|
|
1899
|
+
align-items: center;
|
|
1900
|
+
gap: var(--spacing-2);
|
|
1901
|
+
inline-size: 100%;
|
|
1902
|
+
min-block-size: 3.5rem;
|
|
1903
|
+
padding: var(--spacing-2);
|
|
1904
|
+
border-block-end: var(--border-width-default) solid hsl(var(--border));
|
|
1905
|
+
background-color: hsl(var(--background));
|
|
1906
|
+
font-family: var(--font-family-sans);
|
|
1907
|
+
}
|
|
1908
|
+
.kx-navigation-bar__leading { display: flex; flex-shrink: 0; align-items: center; min-inline-size: 2.25rem; }
|
|
1909
|
+
.kx-navigation-bar__leading:empty { min-inline-size: 0; }
|
|
1910
|
+
.kx-navigation-bar__back {
|
|
1911
|
+
display: inline-flex;
|
|
1912
|
+
align-items: center;
|
|
1913
|
+
justify-content: center;
|
|
1914
|
+
inline-size: 2.25rem;
|
|
1915
|
+
block-size: 2.25rem;
|
|
1916
|
+
margin: 0;
|
|
1917
|
+
padding: 0;
|
|
1918
|
+
border: 0;
|
|
1919
|
+
border-radius: var(--radius-full);
|
|
1920
|
+
background: transparent;
|
|
1921
|
+
color: hsl(var(--foreground));
|
|
1922
|
+
cursor: pointer;
|
|
1923
|
+
transition:
|
|
1924
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
1925
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
1926
|
+
}
|
|
1927
|
+
.kx-navigation-bar__glyph { font-size: 1.75rem; line-height: 1; transform: translateY(-0.06em); }
|
|
1928
|
+
@media (hover: hover) {
|
|
1929
|
+
.kx-navigation-bar__back:hover { background-color: hsl(var(--foreground) / 0.08); }
|
|
1930
|
+
}
|
|
1931
|
+
.kx-navigation-bar__back:active { background-color: hsl(var(--foreground) / 0.12); }
|
|
1932
|
+
.kx-navigation-bar__back:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1933
|
+
.kx-navigation-bar__titles { display: flex; flex: 1 1 5em; flex-direction: column; min-inline-size: 0; overflow-wrap: break-word; }
|
|
1934
|
+
.kx-navigation-bar__title { font: var(--text-body-lg); font-weight: var(--font-weight-medium); color: hsl(var(--foreground)); }
|
|
1935
|
+
.kx-navigation-bar__info { font: var(--text-body-sm); color: hsl(var(--muted-foreground)); }
|
|
1936
|
+
.kx-navigation-bar__actions { display: flex; flex-shrink: 0; align-items: center; gap: var(--spacing-1); margin-inline-start: auto; }
|
|
1937
|
+
.kx-navigation-bar__actions:empty { display: none; }
|
|
1938
|
+
|
|
1939
|
+
/* ── app bar ────────────────────────────────────────────────────────────── */
|
|
1940
|
+
/* A grid, so the one <nav> can sit inline between brand and actions on a wide screen and span a row of its
|
|
1941
|
+
own under the bar on a narrow one — without a second copy of the links. The DOM (and so Tab) order is brand,
|
|
1942
|
+
menu button, nav, actions, and the narrow layout follows it: the button ends the first row, the opened links
|
|
1943
|
+
come under it, and the actions take the last row — so focus never moves backwards across the screen. */
|
|
1944
|
+
.kx-app-bar {
|
|
1945
|
+
display: grid;
|
|
1946
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
1947
|
+
grid-template-areas: 'brand toggle' 'nav nav' 'actions actions';
|
|
1948
|
+
align-items: center;
|
|
1949
|
+
column-gap: var(--spacing-3);
|
|
1950
|
+
inline-size: 100%;
|
|
1951
|
+
padding-inline: var(--spacing-4);
|
|
1952
|
+
border-block-end: var(--border-width-default) solid hsl(var(--border));
|
|
1953
|
+
background-color: hsl(var(--background));
|
|
1954
|
+
font-family: var(--font-family-sans);
|
|
1955
|
+
}
|
|
1956
|
+
.kx-app-bar--sticky { position: sticky; inset-block-start: 0; z-index: var(--z-index-sticky); }
|
|
1957
|
+
.kx-app-bar__brand {
|
|
1958
|
+
grid-area: brand;
|
|
1959
|
+
display: inline-flex;
|
|
1960
|
+
align-items: center;
|
|
1961
|
+
gap: var(--spacing-2);
|
|
1962
|
+
min-block-size: 3.5rem;
|
|
1963
|
+
min-inline-size: 0;
|
|
1964
|
+
border-radius: var(--radius-sm);
|
|
1965
|
+
color: hsl(var(--foreground));
|
|
1966
|
+
font: var(--text-body-lg);
|
|
1967
|
+
font-weight: var(--font-weight-semibold);
|
|
1968
|
+
text-decoration: none;
|
|
1969
|
+
overflow-wrap: break-word;
|
|
1970
|
+
}
|
|
1971
|
+
a.kx-app-bar__brand:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1972
|
+
.kx-app-bar__actions { grid-area: actions; display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-2); padding-block-end: var(--spacing-2); }
|
|
1973
|
+
.kx-app-bar__toggle {
|
|
1974
|
+
grid-area: toggle;
|
|
1975
|
+
display: inline-flex;
|
|
1976
|
+
align-items: center;
|
|
1977
|
+
justify-content: center;
|
|
1978
|
+
inline-size: 2.25rem;
|
|
1979
|
+
block-size: 2.25rem;
|
|
1980
|
+
margin: 0;
|
|
1981
|
+
padding: 0;
|
|
1982
|
+
border: 0;
|
|
1983
|
+
border-radius: var(--radius-control);
|
|
1984
|
+
background: transparent;
|
|
1985
|
+
color: hsl(var(--foreground));
|
|
1986
|
+
cursor: pointer;
|
|
1987
|
+
transition:
|
|
1988
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
1989
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
1990
|
+
}
|
|
1991
|
+
.kx-app-bar__toggle > svg { inline-size: 1.25rem; block-size: 1.25rem; }
|
|
1992
|
+
@media (hover: hover) {
|
|
1993
|
+
.kx-app-bar__toggle:hover { background-color: hsl(var(--foreground) / 0.08); }
|
|
1994
|
+
}
|
|
1995
|
+
.kx-app-bar__toggle:active { background-color: hsl(var(--foreground) / 0.12); }
|
|
1996
|
+
.kx-app-bar__toggle:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
1997
|
+
.kx-app-bar__toggle[aria-expanded='true'] { background-color: hsl(var(--foreground) / 0.08); }
|
|
1998
|
+
.kx-app-bar__nav { grid-area: nav; }
|
|
1999
|
+
.kx-app-bar__links { display: flex; flex-direction: column; gap: var(--spacing-1); padding-block: var(--spacing-2) var(--spacing-3); }
|
|
2000
|
+
.kx-app-bar__links > .kx-app-bar__link { justify-content: flex-start; padding-inline: var(--spacing-3); white-space: normal; }
|
|
2001
|
+
/* current: foreground text on the accent surface AND a 2px `--primary` indicator flush with the block-end edge:
|
|
2002
|
+
the link's own (otherwise transparent) border plus 1px inset, because an inset shadow alone starts inside the
|
|
2003
|
+
border and would float 1px above the edge */
|
|
2004
|
+
.kx-app-bar__link[aria-current='page'] {
|
|
2005
|
+
color: hsl(var(--foreground));
|
|
2006
|
+
background-color: hsl(var(--accent));
|
|
2007
|
+
border-block-end-color: hsl(var(--primary));
|
|
2008
|
+
box-shadow: inset 0 calc(1px - var(--border-width-focus)) 0 hsl(var(--primary));
|
|
2009
|
+
}
|
|
2010
|
+
.kx-app-bar__link[aria-current='page']:focus-visible { box-shadow: inset 0 calc(1px - var(--border-width-focus)) 0 hsl(var(--primary)), var(--shadow-focus); }
|
|
2011
|
+
@media (min-width: 48rem) {
|
|
2012
|
+
.kx-app-bar { grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: 'brand nav actions'; column-gap: var(--spacing-6); }
|
|
2013
|
+
.kx-app-bar__actions { padding-block-end: 0; }
|
|
2014
|
+
.kx-app-bar__toggle { display: none; }
|
|
2015
|
+
/* the disclosure is a narrow-screen behaviour; wide, the links are simply there */
|
|
2016
|
+
.kx-app-bar__nav.kx-disclosure { display: block; visibility: visible; transition: none; }
|
|
2017
|
+
.kx-app-bar__links { flex-direction: row; flex-wrap: wrap; align-items: center; padding-block: var(--spacing-2); }
|
|
2018
|
+
.kx-app-bar__links > .kx-app-bar__link { padding-inline: var(--spacing-3); white-space: nowrap; }
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/* ── footer ─────────────────────────────────────────────────────────────── */
|
|
2022
|
+
.kx-footer {
|
|
2023
|
+
display: block;
|
|
2024
|
+
inline-size: 100%;
|
|
2025
|
+
padding: var(--spacing-10) var(--spacing-6);
|
|
2026
|
+
border-block-start: var(--border-width-default) solid hsl(var(--border));
|
|
2027
|
+
background-color: hsl(var(--background));
|
|
2028
|
+
font-family: var(--font-family-sans);
|
|
2029
|
+
}
|
|
2030
|
+
.kx-footer__columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); gap: var(--spacing-8) var(--spacing-6); }
|
|
2031
|
+
.kx-footer__column { display: flex; flex-direction: column; gap: var(--spacing-3); min-inline-size: 0; }
|
|
2032
|
+
.kx-footer__title { font: var(--text-label-lg); font-weight: var(--font-weight-medium); color: hsl(var(--foreground)); }
|
|
2033
|
+
.kx-footer__links { display: flex; flex-direction: column; align-items: flex-start; gap: var(--spacing-2); }
|
|
2034
|
+
.kx-footer__link {
|
|
2035
|
+
border-radius: var(--radius-sm);
|
|
2036
|
+
color: hsl(var(--muted-foreground));
|
|
2037
|
+
font: var(--text-body-sm);
|
|
2038
|
+
text-decoration: underline;
|
|
2039
|
+
text-decoration-color: transparent;
|
|
2040
|
+
text-underline-offset: 3px;
|
|
2041
|
+
overflow-wrap: break-word;
|
|
2042
|
+
transition:
|
|
2043
|
+
color var(--duration-fast) var(--easing-standard),
|
|
2044
|
+
text-decoration-color var(--duration-fast) var(--easing-standard),
|
|
2045
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
2046
|
+
}
|
|
2047
|
+
@media (hover: hover) {
|
|
2048
|
+
.kx-footer__link:hover { color: hsl(var(--foreground)); text-decoration-color: currentColor; }
|
|
2049
|
+
}
|
|
2050
|
+
.kx-footer__link:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
|
|
2051
|
+
.kx-footer__bottom {
|
|
2052
|
+
display: flex;
|
|
2053
|
+
flex-wrap: wrap;
|
|
2054
|
+
align-items: center;
|
|
2055
|
+
justify-content: space-between;
|
|
2056
|
+
gap: var(--spacing-3);
|
|
2057
|
+
margin-block-start: var(--spacing-8);
|
|
2058
|
+
padding-block-start: var(--spacing-6);
|
|
2059
|
+
border-block-start: var(--border-width-default) solid hsl(var(--border));
|
|
2060
|
+
font: var(--text-body-sm);
|
|
2061
|
+
color: hsl(var(--muted-foreground));
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
/* Disclosure and navigation motion carries no information a reduced-motion reader needs: the expanded or
|
|
2065
|
+
collapsed state, the chevron's direction and every state colour are rendered directly. */
|
|
2066
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2067
|
+
.kx-disclosure,
|
|
2068
|
+
.kx-disclosure[data-state='open'],
|
|
2069
|
+
.kx-accordion__trigger,
|
|
2070
|
+
.kx-accordion__chevron,
|
|
2071
|
+
.kx-breadcrumb__link,
|
|
2072
|
+
.kx-pagination__link,
|
|
2073
|
+
.kx-app-bar__link,
|
|
2074
|
+
.kx-app-bar__toggle,
|
|
2075
|
+
.kx-toc__link,
|
|
2076
|
+
.kx-tab-bar__item,
|
|
2077
|
+
.kx-tab-bar__icon,
|
|
2078
|
+
.kx-navigation-bar__back,
|
|
2079
|
+
.kx-footer__link { transition: none; }
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
/* ── overlays: dialog family and floating surfaces ───────────────────────── */
|
|
2083
|
+
/* TOKENS.md, "Overlays". Every surface here renders in the browser's top layer (overlay.ts), so none of them
|
|
2084
|
+
carries a z-index: stacking is the order surfaces opened in. Both box types arrive with user-agent styles
|
|
2085
|
+
(`dialog` and `[popover]` are centred, bordered, painted `Canvas`); each is reset to the token contract.
|
|
2086
|
+
|
|
2087
|
+
Elevation (TOKENS.md, "Elevation contract"): a floating surface is `shadow-lg`, one step above a resting
|
|
2088
|
+
card's `md`, so a popover over a card reads as above it; a modal surface is `shadow-xl` over a scrim.
|
|
2089
|
+
|
|
2090
|
+
Motion: the open state is reached through `@starting-style` and left through `transition-behavior:
|
|
2091
|
+
allow-discrete` on `display` and `overlay`, so a closing surface keeps painting for its exit while it is
|
|
2092
|
+
already closed to the accessibility tree, the keyboard and the pointer. */
|
|
2093
|
+
|
|
2094
|
+
.kx-overlay-root { display: contents; }
|
|
2095
|
+
|
|
2096
|
+
/* modal surfaces: dialog, alert dialog, modal, sheet, drawer */
|
|
2097
|
+
.kx-dialog {
|
|
2098
|
+
position: fixed;
|
|
2099
|
+
margin: auto;
|
|
2100
|
+
padding: var(--spacing-6);
|
|
2101
|
+
inline-size: min(32rem, 100% - 2 * var(--spacing-4));
|
|
2102
|
+
max-inline-size: none;
|
|
2103
|
+
max-block-size: calc(100% - 2 * var(--spacing-4));
|
|
2104
|
+
overflow: auto;
|
|
2105
|
+
overscroll-behavior: contain;
|
|
2106
|
+
container-type: inline-size;
|
|
2107
|
+
border: var(--border-width-default) solid hsl(var(--border));
|
|
2108
|
+
border-radius: var(--radius-surface);
|
|
2109
|
+
background-color: hsl(var(--popover));
|
|
2110
|
+
color: hsl(var(--popover-foreground));
|
|
2111
|
+
box-shadow: var(--shadow-xl);
|
|
2112
|
+
font-family: var(--font-family-sans);
|
|
2113
|
+
outline: none;
|
|
2114
|
+
/* closed, and where opening starts from */
|
|
2115
|
+
opacity: 0;
|
|
2116
|
+
scale: 0.95;
|
|
2117
|
+
transition:
|
|
2118
|
+
opacity var(--duration-instant) var(--easing-exit),
|
|
2119
|
+
scale var(--duration-instant) var(--easing-exit),
|
|
2120
|
+
overlay var(--duration-instant) var(--easing-exit) allow-discrete,
|
|
2121
|
+
display var(--duration-instant) var(--easing-exit) allow-discrete;
|
|
2122
|
+
}
|
|
2123
|
+
.kx-dialog:not([open]) { pointer-events: none; }
|
|
2124
|
+
.kx-dialog[open] {
|
|
2125
|
+
display: flex;
|
|
2126
|
+
flex-direction: column;
|
|
2127
|
+
gap: var(--spacing-4);
|
|
2128
|
+
opacity: 1;
|
|
2129
|
+
scale: 1;
|
|
2130
|
+
transition-duration: var(--duration-fast);
|
|
2131
|
+
transition-timing-function: var(--easing-enter);
|
|
2132
|
+
}
|
|
2133
|
+
@starting-style {
|
|
2134
|
+
.kx-dialog[open] { opacity: 0; scale: 0.95; }
|
|
2135
|
+
}
|
|
2136
|
+
/* The scrim: the `scrim` role (black at 40% in light, 60% in dark) — not `foreground`, which is near-white in
|
|
2137
|
+
dark and would lift the page behind the dialog toward the dialog's own surface instead of setting it back.
|
|
2138
|
+
The role carries its own alpha. The fallback keeps a browser that does not pass custom properties through to
|
|
2139
|
+
`::backdrop` drawing the light scrim rather than nothing. */
|
|
2140
|
+
.kx-dialog::backdrop {
|
|
2141
|
+
background-color: hsl(var(--scrim, 0 0% 0% / 0.4));
|
|
2142
|
+
opacity: 0;
|
|
2143
|
+
transition:
|
|
2144
|
+
opacity var(--duration-instant) var(--easing-exit),
|
|
2145
|
+
overlay var(--duration-instant) var(--easing-exit) allow-discrete,
|
|
2146
|
+
display var(--duration-instant) var(--easing-exit) allow-discrete;
|
|
2147
|
+
}
|
|
2148
|
+
.kx-dialog[open]::backdrop { opacity: 1; transition-duration: var(--duration-fast); transition-timing-function: var(--easing-enter); }
|
|
2149
|
+
@starting-style {
|
|
2150
|
+
.kx-dialog[open]::backdrop { opacity: 0; }
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
.kx-dialog__header { display: flex; flex-direction: column; gap: var(--spacing-2); }
|
|
2154
|
+
.kx-dialog:has(> .kx-dialog__close) .kx-dialog__header { padding-inline-end: var(--spacing-8); }
|
|
2155
|
+
.kx-dialog__title { margin: 0; font: var(--text-title-dialog); color: hsl(var(--popover-foreground)); overflow-wrap: break-word; }
|
|
2156
|
+
.kx-dialog__description { margin: 0; font: var(--text-body-md); color: hsl(var(--muted-foreground)); overflow-wrap: break-word; }
|
|
2157
|
+
/* Actions toward the inline end, in DOM order. In a narrow surface they stack — in the same order, so the
|
|
2158
|
+
order on screen is the order Tab takes (React reverses them visually; this does not). */
|
|
2159
|
+
.kx-dialog__footer {
|
|
2160
|
+
display: flex;
|
|
2161
|
+
flex-wrap: wrap;
|
|
2162
|
+
justify-content: flex-end;
|
|
2163
|
+
gap: var(--spacing-2);
|
|
2164
|
+
margin-block-start: var(--spacing-2);
|
|
2165
|
+
}
|
|
2166
|
+
@container (max-width: 24rem) {
|
|
2167
|
+
.kx-dialog__footer { flex-direction: column; align-items: stretch; }
|
|
2168
|
+
.kx-dialog__footer > .kx-btn { white-space: normal; }
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
/* The built-in close button: last in the DOM, drawn at the inline-end of the top edge. */
|
|
2172
|
+
.kx-dialog__close {
|
|
2173
|
+
position: absolute;
|
|
2174
|
+
inset-block-start: var(--spacing-4);
|
|
2175
|
+
inset-inline-end: var(--spacing-4);
|
|
2176
|
+
display: inline-grid;
|
|
2177
|
+
place-items: center;
|
|
2178
|
+
inline-size: 2rem;
|
|
2179
|
+
block-size: 2rem;
|
|
2180
|
+
padding: 0;
|
|
2181
|
+
border: 0;
|
|
2182
|
+
border-radius: var(--radius-control);
|
|
2183
|
+
background: transparent;
|
|
2184
|
+
color: hsl(var(--muted-foreground));
|
|
2185
|
+
cursor: pointer;
|
|
2186
|
+
transition:
|
|
2187
|
+
background-color var(--duration-fast) var(--easing-standard),
|
|
2188
|
+
color var(--duration-fast) var(--easing-standard),
|
|
2189
|
+
box-shadow var(--duration-fast) var(--easing-standard);
|
|
2190
|
+
}
|
|
2191
|
+
.kx-dialog__close svg { inline-size: 1rem; block-size: 1rem; }
|
|
2192
|
+
@media (hover: hover) {
|
|
2193
|
+
.kx-dialog__close:hover { background-color: hsl(var(--foreground) / 0.08); color: hsl(var(--foreground)); }
|
|
2194
|
+
}
|
|
2195
|
+
.kx-dialog__close:active { background-color: hsl(var(--foreground) / 0.12); }
|
|
2196
|
+
.kx-dialog__close:focus-visible { outline: none; box-shadow: var(--shadow-focus); color: hsl(var(--foreground)); }
|
|
2197
|
+
|
|
2198
|
+
/* modal: header (title + close), divider, body, divider, footer — React's Modal */
|
|
2199
|
+
.kx-modal { padding: 0; gap: 0; inline-size: min(30rem, 100% - 2 * var(--spacing-4)); }
|
|
2200
|
+
.kx-modal[open] { gap: 0; }
|
|
2201
|
+
.kx-modal__header {
|
|
2202
|
+
display: flex;
|
|
2203
|
+
align-items: center;
|
|
2204
|
+
gap: var(--spacing-3);
|
|
2205
|
+
padding: var(--spacing-5) var(--spacing-6) var(--spacing-3);
|
|
2206
|
+
}
|
|
2207
|
+
.kx-modal__title { flex: 1; min-inline-size: 0; }
|
|
2208
|
+
.kx-modal__close { position: static; flex: none; }
|
|
2209
|
+
.kx-modal__body {
|
|
2210
|
+
display: flex;
|
|
2211
|
+
flex-direction: column;
|
|
2212
|
+
gap: var(--spacing-3);
|
|
2213
|
+
padding: var(--spacing-5) var(--spacing-6);
|
|
2214
|
+
border-block: var(--border-width-default) solid hsl(var(--border));
|
|
2215
|
+
}
|
|
2216
|
+
.kx-modal__footer {
|
|
2217
|
+
display: flex;
|
|
2218
|
+
flex-wrap: wrap;
|
|
2219
|
+
justify-content: flex-end;
|
|
2220
|
+
gap: var(--spacing-3);
|
|
2221
|
+
padding: var(--spacing-4) var(--spacing-6);
|
|
2222
|
+
}
|
|
2223
|
+
@container (max-width: 24rem) {
|
|
2224
|
+
.kx-modal__footer { flex-direction: column; align-items: stretch; }
|
|
2225
|
+
.kx-modal__footer > .kx-btn { white-space: normal; }
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
/* sheet: a panel against one edge. `start` / `end` are logical, so the edge is chosen by the direction the
|
|
2229
|
+
sheet's own element resolves to, with no direction selector anywhere. The inline sides slide by animating
|
|
2230
|
+
their own logical margin (out past the edge by exactly their size); top and bottom slide by translation. */
|
|
2231
|
+
.kx-sheet {
|
|
2232
|
+
--kx-sheet-size: min(24rem, 100% - 3rem);
|
|
2233
|
+
margin: 0;
|
|
2234
|
+
inset: auto;
|
|
2235
|
+
max-inline-size: none;
|
|
2236
|
+
max-block-size: none;
|
|
2237
|
+
border-radius: 0;
|
|
2238
|
+
border-width: 0;
|
|
2239
|
+
opacity: 1;
|
|
2240
|
+
scale: none;
|
|
2241
|
+
transition:
|
|
2242
|
+
margin var(--duration-base) var(--easing-exit),
|
|
2243
|
+
translate var(--duration-base) var(--easing-exit),
|
|
2244
|
+
overlay var(--duration-base) var(--easing-exit) allow-discrete,
|
|
2245
|
+
display var(--duration-base) var(--easing-exit) allow-discrete;
|
|
2246
|
+
}
|
|
2247
|
+
.kx-sheet[open] { opacity: 1; scale: none; transition-duration: var(--duration-slow); transition-timing-function: var(--easing-enter); }
|
|
2248
|
+
.kx-sheet:is([data-side='start'], [data-side='end']) {
|
|
2249
|
+
inset-block: 0;
|
|
2250
|
+
block-size: 100%;
|
|
2251
|
+
inline-size: var(--kx-sheet-size);
|
|
2252
|
+
padding-block: max(var(--spacing-6), env(safe-area-inset-top)) max(var(--spacing-6), env(safe-area-inset-bottom));
|
|
2253
|
+
}
|
|
2254
|
+
.kx-sheet[data-side='end'] {
|
|
2255
|
+
inset-inline-end: 0;
|
|
2256
|
+
border-inline-start-width: var(--border-width-default);
|
|
2257
|
+
margin-inline-end: calc(-1 * var(--kx-sheet-size));
|
|
2258
|
+
}
|
|
2259
|
+
.kx-sheet[data-side='start'] {
|
|
2260
|
+
inset-inline-start: 0;
|
|
2261
|
+
border-inline-end-width: var(--border-width-default);
|
|
2262
|
+
margin-inline-start: calc(-1 * var(--kx-sheet-size));
|
|
2263
|
+
}
|
|
2264
|
+
.kx-sheet[data-side='end'][open] { margin-inline-end: 0; }
|
|
2265
|
+
.kx-sheet[data-side='start'][open] { margin-inline-start: 0; }
|
|
2266
|
+
@starting-style {
|
|
2267
|
+
.kx-sheet[data-side='end'][open] { margin-inline-end: calc(-1 * var(--kx-sheet-size)); }
|
|
2268
|
+
.kx-sheet[data-side='start'][open] { margin-inline-start: calc(-1 * var(--kx-sheet-size)); }
|
|
2269
|
+
}
|
|
2270
|
+
.kx-sheet:is([data-side='top'], [data-side='bottom']) {
|
|
2271
|
+
inset-inline: 0;
|
|
2272
|
+
inline-size: 100%;
|
|
2273
|
+
max-block-size: 85dvh;
|
|
2274
|
+
}
|
|
2275
|
+
.kx-sheet[data-side='top'] {
|
|
2276
|
+
inset-block-start: 0;
|
|
2277
|
+
border-block-end-width: var(--border-width-default);
|
|
2278
|
+
translate: 0 -100%;
|
|
2279
|
+
padding-block-start: max(var(--spacing-6), env(safe-area-inset-top));
|
|
2280
|
+
}
|
|
2281
|
+
.kx-sheet[data-side='bottom'] {
|
|
2282
|
+
inset-block-end: 0;
|
|
2283
|
+
border-block-start-width: var(--border-width-default);
|
|
2284
|
+
translate: 0 100%;
|
|
2285
|
+
padding-block-end: max(var(--spacing-6), env(safe-area-inset-bottom));
|
|
2286
|
+
}
|
|
2287
|
+
.kx-sheet:is([data-side='top'], [data-side='bottom'])[open] { translate: 0 0; }
|
|
2288
|
+
@starting-style {
|
|
2289
|
+
.kx-sheet[data-side='top'][open] { translate: 0 -100%; }
|
|
2290
|
+
.kx-sheet[data-side='bottom'][open] { translate: 0 100%; }
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
/* drawer: a bottom sheet with rounded top corners and a grab handle */
|
|
2294
|
+
.kx-drawer {
|
|
2295
|
+
padding-block-start: var(--spacing-3);
|
|
2296
|
+
border-start-start-radius: var(--radius-xxl);
|
|
2297
|
+
border-start-end-radius: var(--radius-xxl);
|
|
2298
|
+
}
|
|
2299
|
+
.kx-drawer__handle {
|
|
2300
|
+
flex: none;
|
|
2301
|
+
align-self: center;
|
|
2302
|
+
inline-size: 6.25rem;
|
|
2303
|
+
block-size: var(--spacing-2);
|
|
2304
|
+
border-radius: var(--radius-full);
|
|
2305
|
+
background-color: hsl(var(--muted-foreground) / 0.4);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
/* floating surfaces: popover, hover card, tooltip — placed by overlay.ts, which publishes the room each one
|
|
2309
|
+
has as --kx-available-width / --kx-available-height. Larger text grows the surface until it meets that
|
|
2310
|
+
room, and then it scrolls inside itself rather than running off the screen. */
|
|
2311
|
+
.kx-floating {
|
|
2312
|
+
position: fixed;
|
|
2313
|
+
inset: auto;
|
|
2314
|
+
margin: 0;
|
|
2315
|
+
max-inline-size: var(--kx-available-width, calc(100vw - 2 * var(--spacing-2)));
|
|
2316
|
+
max-block-size: var(--kx-available-height, calc(100vh - 2 * var(--spacing-2)));
|
|
2317
|
+
overflow: auto;
|
|
2318
|
+
overscroll-behavior: contain;
|
|
2319
|
+
padding: var(--spacing-4);
|
|
2320
|
+
border: var(--border-width-default) solid hsl(var(--border));
|
|
2321
|
+
border-radius: var(--radius-control);
|
|
2322
|
+
background-color: hsl(var(--popover));
|
|
2323
|
+
color: hsl(var(--popover-foreground));
|
|
2324
|
+
box-shadow: var(--shadow-lg);
|
|
2325
|
+
font: var(--text-body-md);
|
|
2326
|
+
font-family: var(--font-family-sans);
|
|
2327
|
+
overflow-wrap: break-word;
|
|
2328
|
+
outline: none;
|
|
2329
|
+
opacity: 0;
|
|
2330
|
+
translate: var(--kx-from, 0 0);
|
|
2331
|
+
transition:
|
|
2332
|
+
opacity var(--duration-instant) var(--easing-exit),
|
|
2333
|
+
translate var(--duration-instant) var(--easing-exit),
|
|
2334
|
+
overlay var(--duration-instant) var(--easing-exit) allow-discrete,
|
|
2335
|
+
display var(--duration-instant) var(--easing-exit) allow-discrete;
|
|
2336
|
+
}
|
|
2337
|
+
/* where it settles from: 4px back toward its trigger, on the side it actually opened on (after direction and
|
|
2338
|
+
collisions — `data-placement` is that resolved, physical side) */
|
|
2339
|
+
.kx-floating[data-placement='bottom'] { --kx-from: 0 -4px; }
|
|
2340
|
+
.kx-floating[data-placement='top'] { --kx-from: 0 4px; }
|
|
2341
|
+
.kx-floating[data-placement='left'] { --kx-from: 4px 0; }
|
|
2342
|
+
.kx-floating[data-placement='right'] { --kx-from: -4px 0; }
|
|
2343
|
+
.kx-floating:popover-open {
|
|
2344
|
+
opacity: 1;
|
|
2345
|
+
translate: 0 0;
|
|
2346
|
+
transition-duration: var(--duration-fast);
|
|
2347
|
+
transition-timing-function: var(--easing-enter);
|
|
2348
|
+
}
|
|
2349
|
+
@starting-style {
|
|
2350
|
+
.kx-floating:popover-open { opacity: 0; translate: var(--kx-from, 0 0); }
|
|
2351
|
+
}
|
|
2352
|
+
.kx-popover { inline-size: 18rem; }
|
|
2353
|
+
.kx-hover-card { inline-size: 16rem; }
|
|
2354
|
+
/* content stacks with the token gap; the surface owns the spacing, so its children carry no margins of their own */
|
|
2355
|
+
.kx-popover:popover-open,
|
|
2356
|
+
.kx-hover-card:popover-open { display: flex; flex-direction: column; gap: var(--spacing-2); }
|
|
2357
|
+
.kx-popover > *,
|
|
2358
|
+
.kx-hover-card > * { margin: 0; }
|
|
2359
|
+
/* The tooltip is the inverse surface (`action`), small and quick: it labels, it does not hold content. Its
|
|
2360
|
+
transparent edge costs nothing here and becomes a visible outline under a forced palette, where its fill is
|
|
2361
|
+
flattened to Canvas and the edge is the only thing that says where it is. */
|
|
2362
|
+
.kx-tooltip {
|
|
2363
|
+
inline-size: max-content;
|
|
2364
|
+
max-inline-size: min(20rem, var(--kx-available-width, 20rem));
|
|
2365
|
+
padding: var(--spacing-1) var(--spacing-3);
|
|
2366
|
+
border-color: transparent;
|
|
2367
|
+
background-color: hsl(var(--action));
|
|
2368
|
+
color: hsl(var(--action-foreground));
|
|
2369
|
+
box-shadow: none;
|
|
2370
|
+
font: var(--text-body-sm);
|
|
2371
|
+
font-family: var(--font-family-sans);
|
|
2372
|
+
}
|
|
2373
|
+
.kx-tooltip:popover-open { transition-duration: var(--duration-instant); }
|
|
2374
|
+
|
|
2375
|
+
/* Overlay motion tells the reader where a surface came from; under reduced motion it simply appears and
|
|
2376
|
+
disappears, in the same open and closed states (the discrete `display` / `overlay` hand-off goes with it). */
|
|
2377
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2378
|
+
.kx-dialog,
|
|
2379
|
+
.kx-dialog::backdrop,
|
|
2380
|
+
.kx-dialog__close,
|
|
2381
|
+
.kx-floating { transition: none; }
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
/* ── forced colours ─────────────────────────────────────────────────────── */
|
|
2385
|
+
/* Every focus ring above is a box-shadow, and a forced palette (Windows contrast themes, `forced-colors:
|
|
2386
|
+
active`) removes box-shadow entirely. With one on, the keyboard had no visible position on any control
|
|
2387
|
+
(check:angular-browser, "forced colours": every button, field, tab, toggle and selection control). An
|
|
2388
|
+
outline survives the mode and is painted in a system colour, so it is the indicator here; nothing changes
|
|
2389
|
+
outside it. Parts clipped by their field's `overflow: hidden` (composite inputs, the reveal toggle) draw it
|
|
2390
|
+
inset, and the visually hidden radios of a segment or single-select toggle group draw it on their label. */
|
|
2391
|
+
@media (forced-colors: active) {
|
|
2392
|
+
:is(
|
|
2393
|
+
.kx-btn, .kx-fab, .kx-input, .kx-textarea, .kx-native-select, .kx-checkbox, .kx-switch, .kx-radio__input,
|
|
2394
|
+
.kx-slider__input, .kx-tab, .kx-tab-panel, .kx-toggle, .kx-tag__remove, .kx-banner [kxBannerAction],
|
|
2395
|
+
.kx-inform [kxInformAction], a.kx-card, button.kx-card, .kx-accordion__trigger, .kx-breadcrumb__link,
|
|
2396
|
+
.kx-pagination__link, .kx-toc__link, .kx-tab-bar__item, .kx-navigation-bar__back, .kx-app-bar__toggle,
|
|
2397
|
+
.kx-app-bar__link, a.kx-app-bar__brand, .kx-footer__link, .kx-dialog__close
|
|
2398
|
+
):focus-visible,
|
|
2399
|
+
:is(.kx-segmented__radio, .kx-toggle-group__radio):focus-visible + label,
|
|
2400
|
+
.kx-rating__item:has(.kx-rating__radio:focus-visible),
|
|
2401
|
+
.kx-input-otp:has(.kx-input-otp__input:focus-visible) .kx-input-otp__slot[data-active] {
|
|
2402
|
+
outline: 2px solid Highlight;
|
|
2403
|
+
outline-offset: 2px;
|
|
2404
|
+
}
|
|
2405
|
+
:is(.kx-number-input__input, .kx-password-input__input, .kx-password-input__reveal, .kx-input-group__input, .kx-input-group__button):focus-visible {
|
|
2406
|
+
outline: 2px solid Highlight;
|
|
2407
|
+
outline-offset: -2px;
|
|
2408
|
+
}
|
|
2409
|
+
/* a current destination drawn with a background or an inset shadow would lose its cue to the forced palette;
|
|
2410
|
+
an outline in a system colour keeps "you are here" visible */
|
|
2411
|
+
:is(.kx-app-bar__link, .kx-pagination__link)[aria-current='page'],
|
|
2412
|
+
.kx-tab-bar__item[aria-current='page'] .kx-tab-bar__icon {
|
|
2413
|
+
outline: 2px solid CanvasText;
|
|
2414
|
+
outline-offset: -2px;
|
|
2415
|
+
}
|
|
2416
|
+
/* the slider's track and fill are plain elements, whose backgrounds the mode would flatten to Canvas */
|
|
2417
|
+
.kx-slider__track { forced-color-adjust: none; background-color: GrayText; }
|
|
2418
|
+
.kx-slider__fill { background-color: Highlight; }
|
|
2419
|
+
}
|