@nyaruka/temba-components 0.170.0 → 0.170.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.170.0",
3
+ "version": "0.170.1",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
@@ -101,9 +101,11 @@ export class Button extends LitElement {
101
101
  background: var(--success, #16a34a);
102
102
  color: #fff;
103
103
  }
104
+ /* Hover darkening: static pre-mixed values first for browsers
105
+ without color-mix(), re-derived from the token below. */
104
106
  .attention-button:hover,
105
107
  .affirmative:hover {
106
- background: color-mix(in srgb, var(--success, #16a34a) 88%, black);
108
+ background: #138f41;
107
109
  }
108
110
 
109
111
  /* DS .btn-danger — flat danger fill (no lift). */
@@ -112,7 +114,17 @@ export class Button extends LitElement {
112
114
  color: #fff;
113
115
  }
114
116
  .destructive-button:hover {
115
- background: color-mix(in srgb, var(--danger, #d03f3f) 88%, black);
117
+ background: #b73737;
118
+ }
119
+
120
+ @supports (color: color-mix(in srgb, red, red)) {
121
+ .attention-button:hover,
122
+ .affirmative:hover {
123
+ background: color-mix(in srgb, var(--success, #16a34a) 88%, black);
124
+ }
125
+ .destructive-button:hover {
126
+ background: color-mix(in srgb, var(--danger, #d03f3f) 88%, black);
127
+ }
116
128
  }
117
129
 
118
130
  /* DS .btn-ghost — text-only, hover fills with sunken */
@@ -518,19 +518,15 @@ export class Chat extends RapidElement {
518
518
  same theme vars the flat fills used — color-mix keeps host
519
519
  re-theming working — with a whisper of shadow for depth and
520
520
  a hairline border mixed just darker than the fill so bubbles
521
- hold their edge against the background. */
521
+ hold their edge against the background. Base rules carry flat
522
+ fills and pre-mixed borders for browsers without color-mix()
523
+ (pre-Chrome 111); the @supports block below layers the
524
+ gradients on top. */
522
525
  .bubble {
523
526
  padding: 10px 14px;
524
- background: linear-gradient(
525
- 180deg,
526
- color-mix(in srgb, var(--color-chat-in, #e5e5ea) 62%, white) 0%,
527
- var(--color-chat-in, #e5e5ea) 100%
528
- );
527
+ background: var(--color-chat-in, #e5e5ea);
529
528
  border-radius: 18px;
530
- border: var(
531
- --chat-border-in,
532
- 1px solid color-mix(in srgb, var(--color-chat-in, #e5e5ea) 93%, black)
533
- );
529
+ border: var(--chat-border-in, 1px solid #d5d5da);
534
530
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
535
531
  }
536
532
 
@@ -550,20 +546,40 @@ export class Chat extends RapidElement {
550
546
  }
551
547
 
552
548
  .incoming .bubble {
553
- background: linear-gradient(
554
- 180deg,
555
- color-mix(in srgb, var(--color-chat-out, #007aff) 90%, white) 0%,
556
- var(--color-chat-out, #007aff) 58%,
557
- color-mix(in srgb, var(--color-chat-out, #007aff) 95%, black) 100%
558
- );
559
- border: var(
560
- --chat-border-out,
561
- 1px solid
562
- color-mix(in srgb, var(--color-chat-out, #007aff) 90%, black)
563
- );
549
+ background: var(--color-chat-out, #007aff);
550
+ border: var(--chat-border-out, 1px solid #006ee6);
564
551
  color: white;
565
552
  }
566
553
 
554
+ @supports (color: color-mix(in srgb, red, red)) {
555
+ .bubble {
556
+ background: linear-gradient(
557
+ 180deg,
558
+ color-mix(in srgb, var(--color-chat-in, #e5e5ea) 62%, white) 0%,
559
+ var(--color-chat-in, #e5e5ea) 100%
560
+ );
561
+ border: var(
562
+ --chat-border-in,
563
+ 1px solid
564
+ color-mix(in srgb, var(--color-chat-in, #e5e5ea) 93%, black)
565
+ );
566
+ }
567
+
568
+ .incoming .bubble {
569
+ background: linear-gradient(
570
+ 180deg,
571
+ color-mix(in srgb, var(--color-chat-out, #007aff) 90%, white) 0%,
572
+ var(--color-chat-out, #007aff) 58%,
573
+ color-mix(in srgb, var(--color-chat-out, #007aff) 95%, black) 100%
574
+ );
575
+ border: var(
576
+ --chat-border-out,
577
+ 1px solid
578
+ color-mix(in srgb, var(--color-chat-out, #007aff) 90%, black)
579
+ );
580
+ }
581
+ }
582
+
567
583
  .incoming .latest .bubble {
568
584
  border-bottom-right-radius: 4px;
569
585
  }
@@ -573,8 +589,11 @@ export class Chat extends RapidElement {
573
589
  }
574
590
 
575
591
  /* Notes get the same top-lit gradient treatment as message
576
- bubbles, derived from their sticky-note yellow. */
592
+ bubbles, derived from their sticky-note yellow. The flat fill
593
+ first is the fallback for browsers without color-mix() — all
594
+ literals here, so the invalid gradient drops at parse time. */
577
595
  .note .bubble {
596
+ background: #fffac3;
578
597
  background: linear-gradient(
579
598
  180deg,
580
599
  color-mix(in srgb, #fffac3 62%, white) 0%,
@@ -100,7 +100,10 @@ export default class Label extends LitElement {
100
100
  dark variant shade), so flow stays bluish, group stays purplish,
101
101
  etc. — no grey wash. */
102
102
  .label[class*='pill-'].clickable .mask:hover {
103
- background: color-mix(in oklab, currentColor 10%, transparent);
103
+ /* Neutral wash first for browsers without color-mix() the
104
+ currentColor mix can't fall back per-declaration because
105
+ currentColor keeps it valid until computed-value time. */
106
+ background: rgba(0, 0, 0, 0.06);
104
107
  }
105
108
  .label[class*='pill-'] temba-icon {
106
109
  margin-right: 0;
@@ -121,14 +124,26 @@ export default class Label extends LitElement {
121
124
  margin: 0;
122
125
  border: 0;
123
126
  border-radius: 999px;
124
- background: color-mix(in oklab, currentColor 25%, transparent);
127
+ background: rgba(0, 0, 0, 0.12);
125
128
  color: inherit;
126
129
  opacity: 0.8;
127
130
  --icon-color: currentColor;
128
131
  }
129
132
  .label[class*='pill-'] .remove:hover {
130
133
  opacity: 1;
131
- background: color-mix(in oklab, currentColor 45%, transparent);
134
+ background: rgba(0, 0, 0, 0.22);
135
+ }
136
+
137
+ @supports (color: color-mix(in srgb, red, red)) {
138
+ .label[class*='pill-'].clickable .mask:hover {
139
+ background: color-mix(in oklab, currentColor 10%, transparent);
140
+ }
141
+ .label[class*='pill-'] .remove {
142
+ background: color-mix(in oklab, currentColor 25%, transparent);
143
+ }
144
+ .label[class*='pill-'] .remove:hover {
145
+ background: color-mix(in oklab, currentColor 45%, transparent);
146
+ }
132
147
  }
133
148
 
134
149
  /* When a removable X is present, tighten the mask's left padding
@@ -371,14 +371,27 @@ export class Select<T extends SelectOption> extends FieldElement {
371
371
  margin: 0;
372
372
  border: 0;
373
373
  border-radius: 999px;
374
- background: color-mix(in oklab, currentColor 25%, transparent);
374
+ /* Neutral wash first for browsers without color-mix(); the
375
+ currentColor-tinted version below needs the @supports gate
376
+ because currentColor keeps the declaration valid until
377
+ computed-value time. */
378
+ background: rgba(0, 0, 0, 0.12);
375
379
  color: inherit;
376
380
  opacity: 0.8;
377
381
  --icon-color: currentColor;
378
382
  }
379
383
  .multi .remove-item:hover {
380
384
  opacity: 1;
381
- background: color-mix(in oklab, currentColor 45%, transparent);
385
+ background: rgba(0, 0, 0, 0.22);
386
+ }
387
+
388
+ @supports (color: color-mix(in srgb, red, red)) {
389
+ .multi .remove-item {
390
+ background: color-mix(in oklab, currentColor 25%, transparent);
391
+ }
392
+ .multi .remove-item:hover {
393
+ background: color-mix(in oklab, currentColor 45%, transparent);
394
+ }
382
395
  }
383
396
 
384
397
  input {
@@ -198,7 +198,8 @@ export class BroadcastList extends ContentList<Broadcast> {
198
198
  }
199
199
  .menu-button.destructive:hover {
200
200
  border-color: #dc2626;
201
- background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
201
+ /* pre-mixed fallback for browsers without color-mix() */
202
+ background: #fdf2f2;
202
203
  }
203
204
  .detail-body {
204
205
  display: flex;
@@ -243,15 +244,25 @@ export class BroadcastList extends ContentList<Broadcast> {
243
244
  align-items: flex-start;
244
245
  gap: 0.6em;
245
246
  padding: 0.9em 1em 1em;
246
- background: color-mix(
247
- in srgb,
248
- var(--sunken, #f1f3f5) 45%,
249
- var(--surface, #fff)
250
- );
247
+ /* pre-mixed fallback for browsers without color-mix() */
248
+ background: #f9fafa;
251
249
  border: 1px solid var(--border, #e4e7ec);
252
250
  border-radius: var(--r);
253
251
  line-height: 1.5;
254
252
  }
253
+
254
+ @supports (color: color-mix(in srgb, red, red)) {
255
+ .menu-button.destructive:hover {
256
+ background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
257
+ }
258
+ .detail-message {
259
+ background: color-mix(
260
+ in srgb,
261
+ var(--sunken, #f1f3f5) 45%,
262
+ var(--surface, #fff)
263
+ );
264
+ }
265
+ }
255
266
  .detail-message temba-expression-highlight {
256
267
  align-self: stretch;
257
268
  white-space: pre-wrap;
@@ -154,24 +154,33 @@ export class ContentList<T = any> extends RapidElement {
154
154
  --sort-gutter: 16px;
155
155
  /* Selected-row wash — accent-50 on its own reads grey, so a
156
156
  touch of the accent-400 rail colour is mixed in to give
157
- the selection a faint accent tint. */
158
- --cl-selected: color-mix(
159
- in oklab,
160
- var(--accent-400) 9%,
161
- var(--accent-50)
162
- );
157
+ the selection a faint accent tint. Statics pre-mixed from
158
+ the default accent for browsers without color-mix(); the
159
+ @supports block below re-derives them from the tokens. */
160
+ --cl-selected: #e5ecf8;
163
161
  /* The dividers bracketing a selected row — the plain grey
164
162
  --border reads as a seam against the wash, so this is the
165
163
  same accent pushed a little further. */
166
- --cl-selected-border: color-mix(
167
- in oklab,
168
- var(--accent-400) 24%,
169
- var(--accent-50)
170
- );
164
+ --cl-selected-border: #cbdaf1;
171
165
  /* Tint shared by the frozen (pinned) columns and the header
172
166
  row, so the header reads as the same quiet sub-panel as the
173
167
  pinned section. */
174
- --cl-pin-bg: color-mix(in oklab, var(--sunken) 35%, var(--surface));
168
+ --cl-pin-bg: #fafbfc;
169
+ }
170
+ @supports (color: color-mix(in srgb, red, red)) {
171
+ :host {
172
+ --cl-selected: color-mix(
173
+ in oklab,
174
+ var(--accent-400) 9%,
175
+ var(--accent-50)
176
+ );
177
+ --cl-selected-border: color-mix(
178
+ in oklab,
179
+ var(--accent-400) 24%,
180
+ var(--accent-50)
181
+ );
182
+ --cl-pin-bg: color-mix(in oklab, var(--sunken) 35%, var(--surface));
183
+ }
175
184
  }
176
185
  /* fillWindow — take the slack of a height-bounded flex-column
177
186
  parent so the table scrolls internally; min-height: 0 (set
@@ -296,7 +305,14 @@ export class ContentList<T = any> extends RapidElement {
296
305
  color: var(--danger);
297
306
  }
298
307
  .bulk-action.destructive:hover {
299
- background: color-mix(in oklab, var(--danger) 20%, white);
308
+ /* pre-mixed fallback for browsers without color-mix() */
309
+ background: #f6d9d9;
310
+ }
311
+
312
+ @supports (color: color-mix(in srgb, red, red)) {
313
+ .bulk-action.destructive:hover {
314
+ background: color-mix(in oklab, var(--danger) 20%, white);
315
+ }
300
316
  }
301
317
  .bulk-action temba-icon {
302
318
  --icon-color: currentColor;
@@ -377,7 +377,14 @@ export class FieldList extends EndpointMonitorElement {
377
377
 
378
378
  .menu-button.destructive:hover {
379
379
  border-color: #dc2626;
380
- background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
380
+ /* pre-mixed fallback for browsers without color-mix() */
381
+ background: #fdf2f2;
382
+ }
383
+
384
+ @supports (color: color-mix(in srgb, red, red)) {
385
+ .menu-button.destructive:hover {
386
+ background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
387
+ }
381
388
  }
382
389
 
383
390
  .detail-body {
@@ -273,11 +273,20 @@ export class CampaignEvents extends EndpointMonitorElement {
273
273
  justify-content: center;
274
274
  /* icons inside the dot take the dot's hue */
275
275
  --icon-color: var(--dot-color);
276
- background: color-mix(
277
- in srgb,
278
- var(--dot-color) 12%,
279
- var(--color-widget-bg, #fff)
280
- );
276
+ /* plain widget bg for browsers without color-mix(); the tinted
277
+ fill below needs the @supports gate since var() keeps an
278
+ invalid declaration alive until computed-value time */
279
+ background: var(--color-widget-bg, #fff);
280
+ }
281
+
282
+ @supports (color: color-mix(in srgb, red, red)) {
283
+ .dot {
284
+ background: color-mix(
285
+ in srgb,
286
+ var(--dot-color) 12%,
287
+ var(--color-widget-bg, #fff)
288
+ );
289
+ }
281
290
  }
282
291
 
283
292
  /* the anchor marks the field's date itself - painted with the widget
@@ -470,7 +479,8 @@ export class CampaignEvents extends EndpointMonitorElement {
470
479
 
471
480
  .menu-button.destructive:hover {
472
481
  border-color: #dc2626;
473
- background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
482
+ /* pre-mixed fallback for browsers without color-mix() */
483
+ background: #fdf2f2;
474
484
  }
475
485
 
476
486
  /* the event's action, contained with a leading type label. Children
@@ -481,16 +491,26 @@ export class CampaignEvents extends EndpointMonitorElement {
481
491
  align-items: flex-start;
482
492
  gap: 0.6em;
483
493
  padding: 0.9em 1em 1em;
484
- background: color-mix(
485
- in srgb,
486
- var(--sunken, #f1f3f5) 45%,
487
- var(--surface, #fff)
488
- );
494
+ /* pre-mixed fallback for browsers without color-mix() */
495
+ background: #f9fafa;
489
496
  border: 1px solid var(--border, #e4e7ec);
490
497
  border-radius: var(--r);
491
498
  line-height: 1.5;
492
499
  }
493
500
 
501
+ @supports (color: color-mix(in srgb, red, red)) {
502
+ .menu-button.destructive:hover {
503
+ background: color-mix(in srgb, #dc2626 6%, var(--surface, #fff));
504
+ }
505
+ .detail-action {
506
+ background: color-mix(
507
+ in srgb,
508
+ var(--sunken, #f1f3f5) 45%,
509
+ var(--surface, #fff)
510
+ );
511
+ }
512
+ }
513
+
494
514
  /* the close (✕) in the header's upper right - the square
495
515
  hover-wash button treatment (the pager buttons'), sitting
496
516
  just outboard of the action buttons */
@@ -140,10 +140,18 @@ export class ContactChat extends ContactStoreElement {
140
140
 
141
141
  /* While the contact is in a flow the compose outline carries the
142
142
  flow hue: resting border matches the flow pill border, focus
143
- goes solid flow green */
143
+ goes solid flow green. The pre-mixed static comes first — a
144
+ custom property can't fall back per-declaration, so the
145
+ derived version needs the @supports gate. */
144
146
  .in-flow .compose {
145
- --compose-border: 1px solid
146
- color-mix(in srgb, var(--flow, #16a34a) 25%, white);
147
+ --compose-border: 1px solid #c5e8d2;
148
+ }
149
+
150
+ @supports (color: color-mix(in srgb, red, red)) {
151
+ .in-flow .compose {
152
+ --compose-border: 1px solid
153
+ color-mix(in srgb, var(--flow, #16a34a) 25%, white);
154
+ }
147
155
  }
148
156
 
149
157
  .in-flow .compose temba-compose {
@@ -459,10 +467,18 @@ export class ContactChat extends ContactStoreElement {
459
467
  status-area grey. Fallbacks match the --flow design token for
460
468
  pages without tokens. */
461
469
  .in-flow .contact-status {
462
- background: color-mix(in srgb, var(--flow, #16a34a) 12%, white);
470
+ /* pre-mixed statics first for browsers without color-mix() */
471
+ background: #e3f4e9;
463
472
  color: var(--flow, #16a34a);
464
473
  --icon-color: var(--flow, #16a34a);
465
- border-top-color: color-mix(in srgb, var(--flow, #16a34a) 25%, white);
474
+ border-top-color: #c5e8d2;
475
+ }
476
+
477
+ @supports (color: color-mix(in srgb, red, red)) {
478
+ .in-flow .contact-status {
479
+ background: color-mix(in srgb, var(--flow, #16a34a) 12%, white);
480
+ border-top-color: color-mix(in srgb, var(--flow, #16a34a) 25%, white);
481
+ }
466
482
  }
467
483
 
468
484
  /* compact the interrupt button to the row's caption scale */
@@ -530,7 +546,14 @@ export class ContactChat extends ContactStoreElement {
530
546
  }
531
547
 
532
548
  .in-flow .chat-box {
533
- background: color-mix(in srgb, var(--flow, #16a34a) 12%, white);
549
+ /* pre-mixed static first for browsers without color-mix() */
550
+ background: #e3f4e9;
551
+ }
552
+
553
+ @supports (color: color-mix(in srgb, red, red)) {
554
+ .in-flow .chat-box {
555
+ background: color-mix(in srgb, var(--flow, #16a34a) 12%, white);
556
+ }
534
557
  }
535
558
 
536
559
  .ticket-controls {
@@ -189,24 +189,36 @@ export class ContactTimeline extends EndpointMonitorElement {
189
189
  padding: 0.05em 0.65em;
190
190
  border-radius: 999px;
191
191
  white-space: nowrap;
192
- background: color-mix(
193
- in srgb,
194
- var(--pill-hue) 12%,
195
- var(--color-widget-bg, #fff)
196
- );
197
- border: 1px solid
198
- color-mix(in srgb, var(--pill-hue) 25%, var(--color-widget-bg, #fff));
192
+ /* plain bg / neutral border for browsers without color-mix();
193
+ the hue-tinted versions live in the @supports block below */
194
+ background: var(--color-widget-bg, #fff);
195
+ border: 1px solid var(--pill-hue);
199
196
  color: var(--pill-hue);
200
197
  cursor: pointer;
201
198
  transition: background 100ms ease-in-out;
202
199
  }
203
200
 
204
- .campaign-pill:hover {
205
- background: color-mix(
206
- in srgb,
207
- var(--pill-hue) 22%,
208
- var(--color-widget-bg, #fff)
209
- );
201
+ @supports (color: color-mix(in srgb, red, red)) {
202
+ .campaign-pill {
203
+ background: color-mix(
204
+ in srgb,
205
+ var(--pill-hue) 12%,
206
+ var(--color-widget-bg, #fff)
207
+ );
208
+ border: 1px solid
209
+ color-mix(
210
+ in srgb,
211
+ var(--pill-hue) 25%,
212
+ var(--color-widget-bg, #fff)
213
+ );
214
+ }
215
+ .campaign-pill:hover {
216
+ background: color-mix(
217
+ in srgb,
218
+ var(--pill-hue) 22%,
219
+ var(--color-widget-bg, #fff)
220
+ );
221
+ }
210
222
  }
211
223
 
212
224
  .campaign-pill:focus-visible {
@@ -289,24 +301,34 @@ export class ContactTimeline extends EndpointMonitorElement {
289
301
  }
290
302
 
291
303
  /* past events are filled with a lighter tint of the same hue and keep
292
- the solid border - they're settled, whether real or projected */
304
+ the solid border - they're settled, whether real or projected.
305
+ Plain widget bg first for browsers without color-mix(). */
293
306
  .dot.past {
294
- background: color-mix(
295
- in srgb,
296
- var(--dot-color) 25%,
297
- var(--color-widget-bg, #fff)
298
- );
307
+ background: var(--color-widget-bg, #fff);
299
308
  }
300
309
 
301
310
  /* future events haven't occurred yet - signal that with a dotted
302
311
  border and the faintest tint of the hue inside */
303
312
  .dot.future {
304
313
  border-style: dotted;
305
- background: color-mix(
306
- in srgb,
307
- var(--dot-color) 8%,
308
- var(--color-widget-bg, #fff)
309
- );
314
+ background: var(--color-widget-bg, #fff);
315
+ }
316
+
317
+ @supports (color: color-mix(in srgb, red, red)) {
318
+ .dot.past {
319
+ background: color-mix(
320
+ in srgb,
321
+ var(--dot-color) 25%,
322
+ var(--color-widget-bg, #fff)
323
+ );
324
+ }
325
+ .dot.future {
326
+ background: color-mix(
327
+ in srgb,
328
+ var(--dot-color) 8%,
329
+ var(--color-widget-bg, #fff)
330
+ );
331
+ }
310
332
  }
311
333
 
312
334
  /* the now dot and label both paint with the SPA content background
@@ -20,16 +20,21 @@ export const designTokens = css`
20
20
  Fallback matches the design-system.css default so component-
21
21
  only test harnesses pick up the same brand colour. */
22
22
  --accent: rgb(var(--primary-rgb, 73, 127, 206));
23
- --accent-50: color-mix(in srgb, var(--accent) 6%, white);
24
- --accent-100: color-mix(in srgb, var(--accent) 16%, white);
25
- --accent-200: color-mix(in srgb, var(--accent) 32%, white);
26
- --accent-300: color-mix(in srgb, var(--accent) 60%, white);
23
+ /* Static ramp values are pre-mixed from the default anchor for
24
+ browsers without color-mix() (pre-Chrome 111, e.g. the last
25
+ Chrome available on Windows 7/8.1). The @supports block below
26
+ re-derives them from --accent so host re-theming still works
27
+ where color-mix() is available. */
28
+ --accent-50: #f4f7fc;
29
+ --accent-100: #e2ebf7;
30
+ --accent-200: #c5d6ef;
31
+ --accent-300: #92b2e2;
27
32
  --accent-400: var(--accent);
28
- --accent-500: color-mix(in srgb, var(--accent) 90%, black);
29
- --accent-600: color-mix(in srgb, var(--accent) 80%, black);
30
- --accent-700: color-mix(in srgb, var(--accent) 65%, black);
31
- --accent-800: color-mix(in srgb, var(--accent) 50%, black);
32
- --accent-900: color-mix(in srgb, var(--accent) 35%, black);
33
+ --accent-500: #4272b9;
34
+ --accent-600: #3a66a5;
35
+ --accent-700: #2f5386;
36
+ --accent-800: #244067;
37
+ --accent-900: #1a2c48;
33
38
 
34
39
  /* neutrals */
35
40
  --bg: #f6f7f9;
@@ -124,14 +129,19 @@ export const designTokens = css`
124
129
  error state (e.g. the dropdown popup) reference --focus-muted
125
130
  / --focus-halo directly to skip that override. */
126
131
  --focus: rgb(var(--focus-rgb, 91, 156, 229));
127
- --focus-50: color-mix(in srgb, var(--focus) 12%, white);
128
- --focus-100: color-mix(in srgb, var(--focus) 24%, white);
129
- --focus-200: color-mix(in srgb, var(--focus) 40%, white);
130
- --focus-300: color-mix(in srgb, var(--focus) 60%, white);
131
- --focus-600: color-mix(in srgb, var(--focus) 60%, black);
132
- --focus-700: color-mix(in srgb, var(--focus) 45%, black);
133
- --focus-muted: color-mix(in srgb, var(--focus) 60%, white);
134
- --focus-halo: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent);
132
+ /* Static values pre-mixed from the default focus hue for browsers
133
+ without color-mix(); re-derived in the @supports block below. */
134
+ --focus-50: #ebf3fc;
135
+ --focus-100: #d8e7f9;
136
+ --focus-200: #bdd7f5;
137
+ --focus-300: #9dc4ef;
138
+ --focus-600: #375e89;
139
+ --focus-700: #294667;
140
+ --focus-muted: #9dc4ef;
141
+ /* The halo is a translucent wash, so unlike the opaque ramp steps
142
+ it can keep tracking the theme without color-mix() — rgba(var())
143
+ substitution works wherever custom properties do. */
144
+ --focus-halo: 0 0 0 3px rgba(var(--focus-rgb, 91, 156, 229), 0.3);
135
145
  --color-focus: var(--focus-muted);
136
146
  --widget-box-shadow-focused: var(--focus-halo);
137
147
 
@@ -160,4 +170,34 @@ export const designTokens = css`
160
170
  --temba-select-selected-font-size: 13.5px;
161
171
  --temba-select-min-height: var(--input-h);
162
172
  }
173
+
174
+ /* Where color-mix() is supported, derive the accent / focus ramps
175
+ from their anchors so host pages can re-theme by overriding just
176
+ --primary-rgb / --focus-rgb. Browsers without color-mix() keep the
177
+ static pre-mixed values above. Note the static values can't act as
178
+ per-declaration fallbacks — a custom property is never invalid at
179
+ parse time, so the last declaration always wins — hence the
180
+ @supports gate. */
181
+ @supports (color: color-mix(in srgb, red, red)) {
182
+ :host {
183
+ --accent-50: color-mix(in srgb, var(--accent) 6%, white);
184
+ --accent-100: color-mix(in srgb, var(--accent) 16%, white);
185
+ --accent-200: color-mix(in srgb, var(--accent) 32%, white);
186
+ --accent-300: color-mix(in srgb, var(--accent) 60%, white);
187
+ --accent-500: color-mix(in srgb, var(--accent) 90%, black);
188
+ --accent-600: color-mix(in srgb, var(--accent) 80%, black);
189
+ --accent-700: color-mix(in srgb, var(--accent) 65%, black);
190
+ --accent-800: color-mix(in srgb, var(--accent) 50%, black);
191
+ --accent-900: color-mix(in srgb, var(--accent) 35%, black);
192
+
193
+ --focus-50: color-mix(in srgb, var(--focus) 12%, white);
194
+ --focus-100: color-mix(in srgb, var(--focus) 24%, white);
195
+ --focus-200: color-mix(in srgb, var(--focus) 40%, white);
196
+ --focus-300: color-mix(in srgb, var(--focus) 60%, white);
197
+ --focus-600: color-mix(in srgb, var(--focus) 60%, black);
198
+ --focus-700: color-mix(in srgb, var(--focus) 45%, black);
199
+ --focus-muted: color-mix(in srgb, var(--focus) 60%, white);
200
+ --focus-halo: 0 0 0 3px color-mix(in srgb, var(--focus) 30%, transparent);
201
+ }
202
+ }
163
203
  `;