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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +50 -0
- data/LICENSE.md +21 -0
- data/README.md +540 -0
- data/Rakefile +34 -0
- data/app/assets/stylesheets/railsui_charts.css +710 -0
- data/app/controllers/railsui_charts/demo_controller.rb +27 -0
- data/app/javascript/controllers/railsui_chart_controller.js +438 -0
- data/app/javascript/controllers/railsui_chart_filters_controller.js +14 -0
- data/app/javascript/controllers/railsui_metric_dialog_controller.js +27 -0
- data/app/views/railsui_charts/demo/index.html.erb +45 -0
- data/config/routes.rb +4 -0
- data/lib/generators/railsui_charts/install/install_generator.rb +52 -0
- data/lib/generators/railsui_charts/install/templates/railsui_chart_controller.js +438 -0
- data/lib/generators/railsui_charts/install/templates/railsui_chart_filters_controller.js +14 -0
- data/lib/generators/railsui_charts/install/templates/railsui_metric_dialog_controller.js +27 -0
- data/lib/railsui_charts/apex_options_builder.rb +1027 -0
- data/lib/railsui_charts/chart_helper.rb +195 -0
- data/lib/railsui_charts/configuration.rb +89 -0
- data/lib/railsui_charts/engine.rb +19 -0
- data/lib/railsui_charts/filter_helper.rb +90 -0
- data/lib/railsui_charts/filters.rb +130 -0
- data/lib/railsui_charts/interval.rb +110 -0
- data/lib/railsui_charts/metric_helper.rb +219 -0
- data/lib/railsui_charts/version.rb +5 -0
- data/lib/railsui_charts.rb +26 -0
- metadata +86 -0
|
@@ -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
|
+
}
|