railsui_charts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,710 @@
1
+ :root {
2
+ --rui-chart-primary: #6366f1;
3
+ --rui-chart-secondary: #0ea5e9;
4
+ --rui-chart-accent: #10b981;
5
+ --rui-chart-muted: #94a3b8;
6
+
7
+ /* Categorical slots. Assigned in order and never cycled — the order is what
8
+ keeps adjacent series distinguishable under protanopia and deuteranopia,
9
+ so reorder these only after re-validating the palette. Verified against
10
+ both the white and #171717 surfaces. */
11
+ --rui-chart-series-1: #6366f1;
12
+ --rui-chart-series-2: #ea580c;
13
+ --rui-chart-series-3: #db2777;
14
+ --rui-chart-series-4: #c026d3;
15
+ --rui-chart-series-5: #65a30d;
16
+ --rui-chart-series-6: #0284c7;
17
+ --rui-chart-series-7: #dc2626;
18
+ --rui-chart-series-8: #0d9488;
19
+ --rui-chart-grid: rgba(148, 163, 184, 0.22);
20
+ --rui-chart-text: #64748b;
21
+ --rui-chart-metric-label: #111827;
22
+ --rui-chart-metric-value: #111827;
23
+ --rui-chart-metric-muted: #6b7280;
24
+ --rui-chart-background: transparent;
25
+
26
+ /* Touching marks separate with a gap in the surface colour, never a stroke. */
27
+ --rui-chart-surface: #ffffff;
28
+ --rui-chart-border: rgba(17, 24, 39, 0.14);
29
+ --rui-chart-hover: rgba(17, 24, 39, 0.06);
30
+ --rui-chart-switch-off: #d1d5db;
31
+ --rui-chart-skeleton: #f8f9fa;
32
+ --rui-chart-skeleton-line: rgba(148, 163, 184, 0.13);
33
+ --rui-chart-skeleton-sheen: rgba(255, 255, 255, 0.65);
34
+ --rui-chart-positive: #047857;
35
+ --rui-chart-negative: #b91c1c;
36
+ --rui-chart-tooltip-bg: #ffffff;
37
+ --rui-chart-tooltip-text: #111827;
38
+ --rui-chart-tooltip-muted: #6b7280;
39
+ --rui-chart-tooltip-border: rgba(17, 24, 39, 0.14);
40
+ --rui-chart-tooltip-radius: 0.5rem;
41
+ --rui-chart-tooltip-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
42
+
43
+ /* Type. The first three are read by the chart itself: the options ship them
44
+ as `var()` strings and the Stimulus controller resolves them against this
45
+ element before Apex sees them, which is why a chart can be given smaller
46
+ labels by setting a variable on the card it sits in.
47
+
48
+ The rest style the parts drawn in HTML — tooltip, metric cards, tables.
49
+ They are deliberately a step larger than the chart chrome: axis labels are
50
+ reference marks, and card text is meant to be read. */
51
+ --rui-chart-font-family: inherit;
52
+ --rui-chart-font-size: 12px;
53
+ --rui-chart-font-size-sm: 11px;
54
+ --rui-chart-text-size: 0.8125rem;
55
+ --rui-chart-text-size-sm: 0.75rem;
56
+ --rui-chart-value-size: 1.375rem;
57
+ }
58
+
59
+ @media (prefers-color-scheme: dark) {
60
+ :root {
61
+ --rui-chart-grid: rgba(148, 163, 184, 0.16);
62
+ --rui-chart-text: #94a3b8;
63
+ --rui-chart-metric-label: #f9fafb;
64
+ --rui-chart-metric-value: #f9fafb;
65
+ --rui-chart-metric-muted: #9ca3af;
66
+ --rui-chart-surface: #171717;
67
+ --rui-chart-border: rgba(255, 255, 255, 0.16);
68
+ --rui-chart-hover: rgba(255, 255, 255, 0.08);
69
+ --rui-chart-switch-off: #4b5563;
70
+ --rui-chart-skeleton: rgba(255, 255, 255, 0.05);
71
+ --rui-chart-skeleton-line: rgba(255, 255, 255, 0.06);
72
+ --rui-chart-skeleton-sheen: rgba(255, 255, 255, 0.07);
73
+ --rui-chart-positive: #34d399;
74
+ --rui-chart-negative: #f87171;
75
+ --rui-chart-tooltip-bg: #171717;
76
+ --rui-chart-tooltip-text: #f9fafb;
77
+ --rui-chart-tooltip-muted: #9ca3af;
78
+ --rui-chart-tooltip-border: rgba(255, 255, 255, 0.16);
79
+ --rui-chart-tooltip-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
80
+ }
81
+ }
82
+
83
+ /* The Stimulus controller honours a `dark` class and `data-theme="dark"`, so
84
+ the variables answer to both strategies too. */
85
+ .dark,
86
+ [data-theme="dark"] {
87
+ --rui-chart-grid: rgba(148, 163, 184, 0.16);
88
+ --rui-chart-text: #94a3b8;
89
+ --rui-chart-metric-label: #f9fafb;
90
+ --rui-chart-metric-value: #f9fafb;
91
+ --rui-chart-metric-muted: #9ca3af;
92
+ --rui-chart-surface: #171717;
93
+ --rui-chart-border: rgba(255, 255, 255, 0.16);
94
+ --rui-chart-hover: rgba(255, 255, 255, 0.08);
95
+ --rui-chart-switch-off: #4b5563;
96
+ --rui-chart-skeleton: rgba(255, 255, 255, 0.05);
97
+ --rui-chart-skeleton-line: rgba(255, 255, 255, 0.06);
98
+ --rui-chart-skeleton-sheen: rgba(255, 255, 255, 0.07);
99
+ --rui-chart-positive: #34d399;
100
+ --rui-chart-negative: #f87171;
101
+ --rui-chart-tooltip-bg: #171717;
102
+ --rui-chart-tooltip-text: #f9fafb;
103
+ --rui-chart-tooltip-muted: #9ca3af;
104
+ --rui-chart-tooltip-border: rgba(255, 255, 255, 0.16);
105
+ --rui-chart-tooltip-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
106
+ }
107
+
108
+ .railsui-chart {
109
+ position: relative;
110
+ width: 100%;
111
+ }
112
+
113
+ /* -------------------------------------------------------- small multiples */
114
+
115
+ .railsui-small-multiples {
116
+ display: grid;
117
+ grid-template-columns: minmax(0, 1fr);
118
+ gap: 1rem 1.25rem;
119
+ }
120
+
121
+ @media (min-width: 640px) {
122
+ .railsui-small-multiples {
123
+ grid-template-columns: repeat(var(--rui-small-multiple-columns, 3), minmax(0, 1fr));
124
+ }
125
+ }
126
+
127
+ .railsui-small-multiple__title {
128
+ margin: 0 0 0.25rem;
129
+ font-size: var(--rui-chart-text-size-sm);
130
+ font-weight: 600;
131
+ line-height: 1rem;
132
+ color: var(--rui-chart-metric-value);
133
+ }
134
+
135
+ /* ----------------------------------------------------------------- states */
136
+
137
+ .railsui-chart-state {
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ width: 100%;
142
+ padding: 1rem;
143
+ border: 1px dashed var(--rui-chart-border);
144
+ border-radius: 0.5rem;
145
+ text-align: center;
146
+ }
147
+
148
+ .railsui-chart-state__title {
149
+ margin: 0;
150
+ font-size: var(--rui-chart-text-size);
151
+ font-weight: 500;
152
+ line-height: 1.25rem;
153
+ color: var(--rui-chart-metric-muted);
154
+ }
155
+
156
+ .railsui-chart-state__description {
157
+ margin: 0.25rem 0 0;
158
+ max-width: 44ch;
159
+ font-size: var(--rui-chart-text-size);
160
+ line-height: 1.25rem;
161
+ color: var(--rui-chart-text);
162
+ }
163
+
164
+ .railsui-chart-state--error {
165
+ border-style: solid;
166
+ border-color: var(--rui-chart-negative);
167
+ }
168
+
169
+ .railsui-chart-state--error .railsui-chart-state__title {
170
+ color: var(--rui-chart-negative);
171
+ }
172
+
173
+ .railsui-chart-state--loading {
174
+ border-style: none;
175
+ padding: 0;
176
+ }
177
+
178
+ /* Shared placeholder surface: a fill plus the sheen that crosses it. */
179
+ .railsui-chart-skeleton,
180
+ .railsui-skeleton-bar {
181
+ position: relative;
182
+ overflow: hidden;
183
+ background-color: var(--rui-chart-skeleton);
184
+ }
185
+
186
+ /* Stand-ins for the card's text. The plot area is left empty on purpose — a
187
+ slab where the chart goes claims more about the shape of the data than a
188
+ loading state can know. */
189
+ .railsui-skeleton-bar {
190
+ display: block;
191
+ border-radius: 0.25rem;
192
+ }
193
+
194
+ .railsui-skeleton-bar--label { width: 4.5rem; height: 0.6875rem; margin-bottom: 0.4375rem; }
195
+ .railsui-skeleton-bar--value { width: 9.5rem; height: 1.375rem; margin-bottom: 0.5rem; }
196
+ .railsui-skeleton-bar--meta { width: 7rem; height: 0.75rem; }
197
+ .railsui-skeleton-bar--footer { width: 6.5rem; height: 0.75rem; }
198
+
199
+ /* Gridlines rather than a plain block, so a standalone chart placeholder still
200
+ reads as a chart arriving. */
201
+ .railsui-chart-skeleton {
202
+ display: block;
203
+ width: 100%;
204
+ min-height: inherit;
205
+ align-self: stretch;
206
+ border-radius: 0.5rem;
207
+ /* Its own line colour, softer than a real gridline. A placeholder should
208
+ suggest the shape of what is coming without competing with the charts
209
+ already on screen around it. */
210
+ background-image: repeating-linear-gradient(
211
+ to bottom,
212
+ var(--rui-chart-skeleton-line) 0,
213
+ var(--rui-chart-skeleton-line) 1px,
214
+ transparent 1px,
215
+ transparent 25%
216
+ );
217
+ }
218
+
219
+ /* A sheen sweeping across, rather than the whole block breathing. Pulsing
220
+ opacity dims the gridlines too and reads as a broken element; a sweep reads
221
+ as work in progress. */
222
+ .railsui-chart-skeleton::after,
223
+ .railsui-skeleton-bar::after {
224
+ content: "";
225
+ position: absolute;
226
+ inset: 0;
227
+ transform: translateX(-100%);
228
+ background-image: linear-gradient(
229
+ 90deg,
230
+ transparent 0,
231
+ var(--rui-chart-skeleton-sheen) 50%,
232
+ transparent 100%
233
+ );
234
+ animation: railsui-chart-sweep 1.6s ease-in-out infinite;
235
+ }
236
+
237
+ /* Circular forms get a disc, so the placeholder is the shape of the thing
238
+ being waited on. */
239
+ .railsui-chart-skeleton--circular {
240
+ width: auto;
241
+ aspect-ratio: 1;
242
+ margin: 0 auto;
243
+ border-radius: 9999px;
244
+ background-image: none;
245
+ }
246
+
247
+ @keyframes railsui-chart-sweep {
248
+ 100% { transform: translateX(100%); }
249
+ }
250
+
251
+ /* Turbo marks a frame busy while it fetches. Holding the previous render at
252
+ reduced opacity keeps the numbers on screen and the layout still — a
253
+ skeleton here would throw the chart away and flash. */
254
+ [aria-busy="true"] .railsui-chart,
255
+ [aria-busy="true"] .railsui-metric-card {
256
+ opacity: 0.55;
257
+ transition: opacity 120ms ease;
258
+ }
259
+
260
+ @media (prefers-reduced-motion: reduce) {
261
+ /* The skeleton still reads as a placeholder standing still. */
262
+ .railsui-chart-skeleton::after,
263
+ .railsui-skeleton-bar::after {
264
+ animation: none;
265
+ opacity: 0.4;
266
+ transform: none;
267
+ }
268
+
269
+ [aria-busy="true"] .railsui-chart,
270
+ [aria-busy="true"] .railsui-metric-card {
271
+ transition: none;
272
+ }
273
+ }
274
+
275
+ /* ---------------------------------------------------------------- filters */
276
+
277
+ /* One row above everything it scopes. Never inside a chart card — per-card
278
+ date pickers let two cards disagree about what "this week" means. */
279
+ .railsui-chart-filters {
280
+ display: flex;
281
+ flex-wrap: wrap;
282
+ align-items: center;
283
+ gap: 0.5rem 1rem;
284
+ margin-bottom: 1.25rem;
285
+ }
286
+
287
+ .railsui-chart-filters__field {
288
+ display: flex;
289
+ align-items: center;
290
+ gap: 0.375rem;
291
+ }
292
+
293
+ .railsui-chart-filters__label {
294
+ font-size: var(--rui-chart-text-size);
295
+ color: var(--rui-chart-metric-muted);
296
+ }
297
+
298
+ .railsui-chart-filters__control {
299
+ position: relative;
300
+ display: inline-flex;
301
+ align-items: center;
302
+ }
303
+
304
+ /* Drawn from borders rather than a background image so it inherits
305
+ currentColor and follows the theme without a second asset. */
306
+ .railsui-chart-filters__control::after {
307
+ content: "";
308
+ position: absolute;
309
+ right: 0.6875rem;
310
+ width: 0.375rem;
311
+ height: 0.375rem;
312
+ border-right: 1.5px solid currentColor;
313
+ border-bottom: 1.5px solid currentColor;
314
+ transform: translateY(-25%) rotate(45deg);
315
+ opacity: 0.65;
316
+ pointer-events: none;
317
+ }
318
+
319
+ /* Bare until hover. Filter chrome sits above the data all day; it earns its
320
+ weight only while someone is reaching for it. */
321
+ .railsui-chart-filters__select {
322
+ appearance: none;
323
+ -webkit-appearance: none;
324
+ padding: 0.25rem 1.875rem 0.25rem 0.625rem;
325
+ border: 0;
326
+ border-radius: 9999px;
327
+ background-color: transparent;
328
+ /* Tailwind's forms plugin paints an arrow on every select. The wrapper draws
329
+ ours, so drop that one rather than stacking two chevrons. */
330
+ background-image: none;
331
+ color: var(--rui-chart-metric-value);
332
+ font-family: inherit;
333
+ font-size: var(--rui-chart-text-size);
334
+ font-weight: 500;
335
+ line-height: 1.25rem;
336
+ cursor: pointer;
337
+ transition: background-color 120ms ease;
338
+ }
339
+
340
+ /* A hairline with the wash. On a near-black page a bare fill reads as a soft
341
+ grey blob with no edge; the inset ring gives the pill a boundary without
342
+ reserving border width in the resting state. */
343
+ .railsui-chart-filters__select:hover {
344
+ background-color: var(--rui-chart-hover);
345
+ box-shadow: inset 0 0 0 1px var(--rui-chart-border);
346
+ }
347
+
348
+ .railsui-chart-filters__select:focus-visible {
349
+ outline: 2px solid var(--rui-chart-primary);
350
+ outline-offset: 1px;
351
+ }
352
+
353
+ .railsui-chart-filters__compare {
354
+ position: relative;
355
+ display: inline-flex;
356
+ align-items: center;
357
+ gap: 0.5rem;
358
+ font-size: var(--rui-chart-text-size);
359
+ color: var(--rui-chart-metric-muted);
360
+ cursor: pointer;
361
+ }
362
+
363
+ .railsui-chart-filters__compare--switch {
364
+ padding: 0.25rem 0.75rem 0.25rem 0.5rem;
365
+ border-radius: 9999px;
366
+ transition: background-color 120ms ease;
367
+ }
368
+
369
+ .railsui-chart-filters__compare--switch:hover {
370
+ background-color: var(--rui-chart-hover);
371
+ box-shadow: inset 0 0 0 1px var(--rui-chart-border);
372
+ }
373
+
374
+ /* Kept in the accessibility tree and focusable; the track is what you see. */
375
+ .railsui-chart-filters__switch {
376
+ position: absolute;
377
+ width: 1px;
378
+ height: 1px;
379
+ opacity: 0;
380
+ pointer-events: none;
381
+ }
382
+
383
+ .railsui-chart-filters__track {
384
+ position: relative;
385
+ display: inline-block;
386
+ flex-shrink: 0;
387
+ width: 1.75rem;
388
+ height: 1rem;
389
+ border-radius: 9999px;
390
+ background-color: var(--rui-chart-switch-off);
391
+ transition: background-color 140ms ease;
392
+ }
393
+
394
+ .railsui-chart-filters__track::after {
395
+ content: "";
396
+ position: absolute;
397
+ top: 0.125rem;
398
+ left: 0.125rem;
399
+ width: 0.75rem;
400
+ height: 0.75rem;
401
+ border-radius: 9999px;
402
+ background-color: #ffffff;
403
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.25);
404
+ transition: transform 140ms ease;
405
+ }
406
+
407
+ .railsui-chart-filters__switch:checked + .railsui-chart-filters__track {
408
+ background-color: var(--rui-chart-primary);
409
+ }
410
+
411
+ .railsui-chart-filters__switch:checked + .railsui-chart-filters__track::after {
412
+ transform: translateX(0.75rem);
413
+ }
414
+
415
+ .railsui-chart-filters__switch:focus-visible + .railsui-chart-filters__track {
416
+ outline: 2px solid var(--rui-chart-primary);
417
+ outline-offset: 2px;
418
+ }
419
+
420
+ .railsui-chart-filters__apply {
421
+ padding: 0.25rem 0.75rem;
422
+ border: 1px solid var(--rui-chart-border);
423
+ border-radius: 9999px;
424
+ background-color: var(--rui-chart-surface);
425
+ font-family: inherit;
426
+ font-size: var(--rui-chart-text-size);
427
+ }
428
+
429
+ @media (prefers-reduced-motion: reduce) {
430
+ .railsui-chart-filters__select,
431
+ .railsui-chart-filters__compare--switch,
432
+ .railsui-chart-filters__track,
433
+ .railsui-chart-filters__track::after {
434
+ transition: none;
435
+ }
436
+ }
437
+
438
+ /* ---------------------------------------------------------------- metric */
439
+
440
+ .railsui-metric {
441
+ display: flex;
442
+ flex-direction: column;
443
+ gap: 0.375rem;
444
+ }
445
+
446
+ .railsui-metric-label {
447
+ margin: 0;
448
+ font-size: var(--rui-chart-text-size);
449
+ font-weight: 500;
450
+ color: var(--rui-chart-metric-muted);
451
+ line-height: 1.25rem;
452
+ }
453
+
454
+ .railsui-metric-value-row {
455
+ display: flex;
456
+ align-items: baseline;
457
+ gap: 0.5rem;
458
+ }
459
+
460
+ .railsui-metric-value {
461
+ font-size: var(--rui-chart-value-size);
462
+ font-weight: 600;
463
+ line-height: 1.15;
464
+ color: var(--rui-chart-metric-value);
465
+ }
466
+
467
+ /* ----------------------------------------------------------- metric card */
468
+
469
+ .railsui-metric-card {
470
+ display: flex;
471
+ flex-direction: column;
472
+ padding: 1rem 1.125rem 0.75rem;
473
+ border: 1px solid var(--rui-chart-border);
474
+ border-radius: 0.5rem;
475
+ background-color: var(--rui-chart-surface);
476
+ }
477
+
478
+ .railsui-metric-card__head {
479
+ display: flex;
480
+ flex-direction: column;
481
+ gap: 0.125rem;
482
+ }
483
+
484
+ .railsui-metric-card__label {
485
+ margin: 0;
486
+ font-size: var(--rui-chart-text-size);
487
+ font-weight: 600;
488
+ line-height: 1.25rem;
489
+ color: var(--rui-chart-metric-label);
490
+ }
491
+
492
+ .railsui-metric-card__value-row {
493
+ display: flex;
494
+ align-items: baseline;
495
+ gap: 0.4375rem;
496
+ }
497
+
498
+ .railsui-metric-card__value {
499
+ font-size: var(--rui-chart-value-size);
500
+ font-weight: 600;
501
+ line-height: 1.25;
502
+ color: var(--rui-chart-metric-value);
503
+ /* Proportional figures: tabular digits make a large standalone number
504
+ look loose at display sizes. */
505
+ font-variant-numeric: proportional-nums;
506
+ }
507
+
508
+ .railsui-metric-card__previous {
509
+ margin: 0;
510
+ font-size: var(--rui-chart-text-size);
511
+ line-height: 1.25rem;
512
+ color: var(--rui-chart-metric-muted);
513
+ }
514
+
515
+ .railsui-metric-card__chart {
516
+ margin-top: 0.5rem;
517
+ }
518
+
519
+ /* Rendered outside the plot so the first and last labels sit flush with the
520
+ card edges. Apex centres them on their data point and clips at the canvas
521
+ edge, which costs either the label's first characters or plot width. */
522
+ .railsui-metric-card__axis {
523
+ display: flex;
524
+ align-items: baseline;
525
+ justify-content: space-between;
526
+ gap: 1rem;
527
+ margin-top: 0.125rem;
528
+ font-size: var(--rui-chart-text-size-sm);
529
+ line-height: 1rem;
530
+ color: var(--rui-chart-text);
531
+ }
532
+
533
+ .railsui-metric-card__footer {
534
+ display: flex;
535
+ align-items: center;
536
+ justify-content: space-between;
537
+ gap: 1rem;
538
+ margin-top: 0.75rem;
539
+ padding-top: 0.625rem;
540
+ border-top: 1px solid var(--rui-chart-border);
541
+ font-size: var(--rui-chart-text-size);
542
+ line-height: 1.25rem;
543
+ }
544
+
545
+ .railsui-metric-card__updated {
546
+ color: var(--rui-chart-metric-muted);
547
+ }
548
+
549
+ .railsui-metric-card__details {
550
+ padding: 0;
551
+ border: 0;
552
+ background: none;
553
+ font-family: inherit;
554
+ font-size: inherit;
555
+ font-weight: 500;
556
+ line-height: inherit;
557
+ color: var(--rui-chart-primary);
558
+ text-decoration: none;
559
+ cursor: pointer;
560
+ }
561
+
562
+ .railsui-metric-card__details:hover {
563
+ text-decoration: underline;
564
+ }
565
+
566
+ /* --------------------------------------------------------- expanded view */
567
+
568
+ .railsui-metric-dialog {
569
+ width: min(56rem, calc(100vw - 2rem));
570
+ max-height: calc(100vh - 4rem);
571
+ padding: 0;
572
+ border: 1px solid var(--rui-chart-border);
573
+ border-radius: 0.75rem;
574
+ background-color: var(--rui-chart-surface);
575
+ color: var(--rui-chart-metric-value);
576
+ overflow: hidden;
577
+ }
578
+
579
+ .railsui-metric-dialog::backdrop {
580
+ background-color: rgba(15, 23, 42, 0.5);
581
+ }
582
+
583
+ .railsui-metric-dialog__panel {
584
+ padding: 1.25rem 1.5rem 1.5rem;
585
+ }
586
+
587
+ .railsui-metric-dialog__head {
588
+ display: flex;
589
+ align-items: flex-start;
590
+ justify-content: space-between;
591
+ gap: 1rem;
592
+ margin-bottom: 1rem;
593
+ }
594
+
595
+ .railsui-metric-dialog__close {
596
+ flex-shrink: 0;
597
+ width: 1.75rem;
598
+ height: 1.75rem;
599
+ border: 0;
600
+ border-radius: 9999px;
601
+ background-color: transparent;
602
+ color: var(--rui-chart-metric-muted);
603
+ font-size: 1.25rem;
604
+ line-height: 1;
605
+ cursor: pointer;
606
+ transition: background-color 120ms ease;
607
+ }
608
+
609
+ .railsui-metric-dialog__close:hover {
610
+ background-color: var(--rui-chart-hover);
611
+ }
612
+
613
+ /* ---------------------------------------------------------------- deltas */
614
+
615
+ /* Plain coloured text. A filled pill competes with the value beside it. */
616
+ .railsui-metric-delta {
617
+ font-size: var(--rui-chart-text-size);
618
+ font-weight: 600;
619
+ line-height: 1.25rem;
620
+ font-variant-numeric: proportional-nums;
621
+ }
622
+
623
+ .railsui-metric-delta--positive {
624
+ color: var(--rui-chart-positive);
625
+ }
626
+
627
+ .railsui-metric-delta--negative {
628
+ color: var(--rui-chart-negative);
629
+ }
630
+
631
+ .railsui-metric-delta--neutral {
632
+ color: var(--rui-chart-metric-muted);
633
+ }
634
+
635
+ /* --------------------------------------------------------------- tooltip */
636
+
637
+ /* Apex ships a dark slab whatever the page is doing. Overriding the container
638
+ lets the tooltip sit on the same surface as the card it belongs to. */
639
+ .railsui-chart .apexcharts-tooltip.apexcharts-theme-light,
640
+ .railsui-chart .apexcharts-tooltip.apexcharts-theme-dark {
641
+ border: 1px solid var(--rui-chart-tooltip-border) !important;
642
+ border-radius: var(--rui-chart-tooltip-radius) !important;
643
+ background: var(--rui-chart-tooltip-bg) !important;
644
+ box-shadow: var(--rui-chart-tooltip-shadow) !important;
645
+ overflow: hidden;
646
+ }
647
+
648
+ .railsui-chart-tooltip {
649
+ min-width: 11rem;
650
+ padding: 0.5rem 0.625rem 0.5625rem;
651
+ font-family: inherit;
652
+ font-size: var(--rui-chart-text-size-sm);
653
+ line-height: 1rem;
654
+ color: var(--rui-chart-tooltip-text);
655
+ }
656
+
657
+ .railsui-chart-tooltip__head {
658
+ display: flex;
659
+ align-items: baseline;
660
+ justify-content: space-between;
661
+ gap: 0.75rem;
662
+ padding-bottom: 0.375rem;
663
+ margin-bottom: 0.375rem;
664
+ border-bottom: 1px solid var(--rui-chart-tooltip-border);
665
+ }
666
+
667
+ .railsui-chart-tooltip__title {
668
+ font-weight: 600;
669
+ }
670
+
671
+ .railsui-chart-tooltip__delta {
672
+ font-weight: 600;
673
+ }
674
+
675
+ .railsui-chart-tooltip__delta--positive { color: var(--rui-chart-positive); }
676
+ .railsui-chart-tooltip__delta--negative { color: var(--rui-chart-negative); }
677
+ .railsui-chart-tooltip__delta--neutral { color: var(--rui-chart-tooltip-muted); }
678
+
679
+ .railsui-chart-tooltip__rows {
680
+ width: 100%;
681
+ border-collapse: collapse;
682
+ }
683
+
684
+ .railsui-chart-tooltip__rows th {
685
+ display: flex;
686
+ align-items: center;
687
+ gap: 0.375rem;
688
+ padding: 0.0625rem 0.75rem 0.0625rem 0;
689
+ font-weight: 400;
690
+ text-align: left;
691
+ color: var(--rui-chart-tooltip-muted);
692
+ white-space: nowrap;
693
+ }
694
+
695
+ /* Values line up in a column, so they read tabular here even though the
696
+ card's headline figure does not. */
697
+ .railsui-chart-tooltip__rows td {
698
+ padding: 0.0625rem 0;
699
+ text-align: right;
700
+ font-weight: 600;
701
+ font-variant-numeric: tabular-nums;
702
+ white-space: nowrap;
703
+ }
704
+
705
+ .railsui-chart-tooltip__key {
706
+ width: 0.5rem;
707
+ height: 0.5rem;
708
+ flex-shrink: 0;
709
+ border-radius: 0.125rem;
710
+ }