@keenmate/pure-admin-core 2.9.0-rc03 → 2.9.0-rc05
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 +55 -13
- package/dist/css/main.css +554 -7
- package/package.json +3 -1
- package/snippets/buttons.html +51 -0
- package/snippets/cards.html +132 -47
- package/snippets/comparison.html +26 -22
- package/snippets/manifest.json +180 -115
- package/snippets/range-group.html +125 -0
- package/snippets/splitter.html +44 -38
- package/snippets/statistics.html +31 -0
- package/src/js/btn-split-auto-absorb.js +327 -0
- package/src/js/command-palette.js +472 -0
- package/src/js/file-selector.js +1275 -0
- package/src/js/internal/logging.js +121 -0
- package/src/js/logic-tree-renderer.js +303 -0
- package/src/js/modal-dialogs.js +460 -0
- package/src/js/overflow.js +371 -0
- package/src/js/pa-stat-fit.js +184 -0
- package/src/js/range-group.js +663 -0
- package/src/js/search-autocomplete-v2.js +907 -0
- package/src/js/search-autocomplete.js +434 -0
- package/src/js/settings-panel.js +245 -0
- package/src/js/split-button.js +141 -0
- package/src/js/splitter.js +1323 -0
- package/src/js/toast-service.js +302 -0
- package/src/js/tooltips-popovers.js +275 -0
- package/src/js/virtual-scroll.js +143 -0
- package/src/js/virtual-textbox.js +803 -0
- package/src/scss/_core.scss +7 -0
- package/src/scss/core-components/_buttons.scss +44 -0
- package/src/scss/core-components/_cards.scss +95 -6
- package/src/scss/core-components/_overflow.scss +50 -0
- package/src/scss/core-components/_range-group.scss +474 -0
- package/src/scss/core-components/_statistics.scss +163 -0
- package/src/scss/variables/_components.scss +41 -2
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/* ========================================
|
|
2
|
+
Range Group
|
|
3
|
+
A compact multi-range filter: one toggle that summarises several
|
|
4
|
+
numeric range filters (e.g. "Age / Salary / Children") inline, and
|
|
5
|
+
expands into a floating panel with one slider per dimension.
|
|
6
|
+
|
|
7
|
+
Two blocks live here, on purpose:
|
|
8
|
+
|
|
9
|
+
- .pa-range — the slider primitive. Single- OR dual-thumb,
|
|
10
|
+
driven entirely by CSS custom properties that JS
|
|
11
|
+
sets (--_pos on each thumb, --_fill-start /
|
|
12
|
+
--_fill-end on the fill). Positioning uses LOGICAL
|
|
13
|
+
properties (inset-inline-*) so RTL mirrors for
|
|
14
|
+
free — JS only ever deals in 0–100 percentages.
|
|
15
|
+
- .pa-range-group — the compact control: a __toggle summarising the
|
|
16
|
+
rows, and a floating __panel of .pa-range rows.
|
|
17
|
+
The panel is anchored with Floating UI (see the
|
|
18
|
+
demo's range-group.js, mirroring split-button.js).
|
|
19
|
+
|
|
20
|
+
The group is meant to drop into a filter-card __filters row as a
|
|
21
|
+
single control that stands in for N sliders. See snippets/range-group.html.
|
|
22
|
+
======================================== */
|
|
23
|
+
|
|
24
|
+
@use '../variables' as *;
|
|
25
|
+
|
|
26
|
+
/* ----------------------------------------
|
|
27
|
+
Slider primitive — pa-range
|
|
28
|
+
---------------------------------------- */
|
|
29
|
+
|
|
30
|
+
// SCSS values are the compile-time DEFAULTS; every one is also exposed as a
|
|
31
|
+
// runtime --pa-range-* token (consumed via `var(--pa-range-x, <default>)`), so
|
|
32
|
+
// a theme or a per-instance inline style can retint/resize a slider without a
|
|
33
|
+
// recompile and without touching the global emit mixin. Colour tokens fall
|
|
34
|
+
// back to the shared framework cascade; dimension tokens fall back to these
|
|
35
|
+
// SCSS values. Overridable at :root, on .pa-mode-*, or on any .pa-range /
|
|
36
|
+
// .pa-range-group ancestor. Runtime tokens:
|
|
37
|
+
// --pa-range-track track colour (→ --pa-surface-track)
|
|
38
|
+
// --pa-range-fill selected-range fill (→ --pa-accent)
|
|
39
|
+
// --pa-range-thumb-bg handle interior (→ --pa-card-bg)
|
|
40
|
+
// --pa-range-thumb-border handle ring/bar/chevron (→ --pa-accent)
|
|
41
|
+
// --pa-range-thumb-border-hover handle on hover (→ --pa-accent-hover)
|
|
42
|
+
// --pa-range-focus-ring thumb focus/active ring (→ --pa-accent-light)
|
|
43
|
+
// --pa-range-tick minor tick-mark colour (→ --pa-border-color)
|
|
44
|
+
// --pa-range-tick-major major tick-mark colour (→ --pa-text-tertiary)
|
|
45
|
+
// --pa-range-track-height track/fill thickness (→ 0.4rem)
|
|
46
|
+
// --pa-range-thumb-size default round handle Ø (→ 1.6rem)
|
|
47
|
+
// --pa-range-group-panel-min-width floating panel width (→ 32rem)
|
|
48
|
+
// NOTE: the __panel is reparented to <body> while open (to escape overflow
|
|
49
|
+
// clipping), so a per-instance override must sit on the __panel or the
|
|
50
|
+
// .pa-range rows (they travel with it) — or at :root / theme level. Tokens
|
|
51
|
+
// set on the .pa-range-group root reach the closed summary but not the open
|
|
52
|
+
// panel.
|
|
53
|
+
$range-track-height: 0.4rem;
|
|
54
|
+
$range-thumb-size: 1.6rem;
|
|
55
|
+
$range-thumb-border: 0.2rem;
|
|
56
|
+
$range-fill-height: $range-track-height;
|
|
57
|
+
|
|
58
|
+
// Tallest handle shape (the bar) drives the reserved height so no shape
|
|
59
|
+
// overflows the row.
|
|
60
|
+
$range-thumb-tall: 2.2rem;
|
|
61
|
+
|
|
62
|
+
// Tick marks. A tick is a short line centred on the track and drawn BEHIND
|
|
63
|
+
// it, so only the ends protrude above/below — major ticks reach further out
|
|
64
|
+
// than minor ones. Sitting behind the track/fill sidesteps any contrast
|
|
65
|
+
// problem over the accent fill. Labels (opt-in) sit in a reserved band below.
|
|
66
|
+
$range-tick-width: 0.1rem;
|
|
67
|
+
$range-tick-major-width: 0.2rem;
|
|
68
|
+
$range-tick-minor-height: 0.9rem; // 0.25rem protrudes each side of a 0.4rem track
|
|
69
|
+
$range-tick-major-height: 1.4rem; // 0.5rem protrudes each side
|
|
70
|
+
$range-tick-label-gap: 0.3rem; // track bottom → label top
|
|
71
|
+
$range-tick-label-space: 1.6rem; // extra row height reserved when labelled
|
|
72
|
+
|
|
73
|
+
.pa-range {
|
|
74
|
+
position: relative;
|
|
75
|
+
// Reserve the tallest handle's height so absolutely-positioned thumbs
|
|
76
|
+
// never clip against the row above/below. The rail centres itself, so
|
|
77
|
+
// circle handles stay centred regardless.
|
|
78
|
+
height: $range-thumb-tall;
|
|
79
|
+
touch-action: none; // let JS own the drag gesture
|
|
80
|
+
user-select: none;
|
|
81
|
+
// The track is click-to-seek (range-group.js moves the nearest thumb), so
|
|
82
|
+
// the whole row reads as clickable; thumbs override with grab/grabbing.
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Labelled ticks reserve a band below the track. Grow the row and keep the
|
|
87
|
+
// rail anchored to the top so the labels drop into the reserved space rather
|
|
88
|
+
// than pushing the track off-centre from its sibling rows.
|
|
89
|
+
.pa-range--ticks-labeled {
|
|
90
|
+
height: calc(#{$range-thumb-tall} + #{$range-tick-label-space});
|
|
91
|
+
|
|
92
|
+
.pa-range__rail {
|
|
93
|
+
top: calc(#{$range-thumb-tall} / 2);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The inner rail the thumbs travel along. Inset half a thumb on each inline
|
|
98
|
+
// side so a thumb centred at 0% / 100% sits fully on the track without
|
|
99
|
+
// overhanging the ends. Everything (track, fill, thumbs) positions against
|
|
100
|
+
// this, so JS only ever works in 0–100% of the rail width.
|
|
101
|
+
.pa-range__rail {
|
|
102
|
+
position: absolute;
|
|
103
|
+
// Inset tracks the handle size so extreme-position thumbs stay on the track
|
|
104
|
+
// even if --pa-range-thumb-size is overridden.
|
|
105
|
+
inset-inline: calc(var(--pa-range-thumb-size, #{$range-thumb-size}) / 2);
|
|
106
|
+
top: 50%;
|
|
107
|
+
transform: translateY(-50%);
|
|
108
|
+
height: var(--pa-range-track-height, #{$range-track-height});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.pa-range__track {
|
|
112
|
+
position: absolute;
|
|
113
|
+
inset: 0;
|
|
114
|
+
border-radius: 999px;
|
|
115
|
+
background-color: var(--pa-range-track, var(--pa-surface-track));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.pa-range__fill {
|
|
119
|
+
position: absolute;
|
|
120
|
+
top: 0;
|
|
121
|
+
bottom: 0;
|
|
122
|
+
height: var(--pa-range-track-height, #{$range-fill-height});
|
|
123
|
+
border-radius: 999px;
|
|
124
|
+
background-color: var(--pa-range-fill, var(--pa-accent));
|
|
125
|
+
// JS sets these as 0–100% — logical, so RTL flips automatically.
|
|
126
|
+
inset-inline-start: var(--_fill-start, 0%);
|
|
127
|
+
inset-inline-end: var(--_fill-end, 0%);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ---- Tick marks ----
|
|
131
|
+
Built by range-group.js when a row has data-ticks. The container lives
|
|
132
|
+
INSIDE the rail (so 0–100% maps to the same travel as the thumbs) and is
|
|
133
|
+
the rail's first child — painted behind the track/fill, so each tick shows
|
|
134
|
+
only as protruding ends. Marks are pointer-events:none, so a track click
|
|
135
|
+
still reaches the .pa-range click-to-seek handler. */
|
|
136
|
+
.pa-range__ticks {
|
|
137
|
+
position: absolute;
|
|
138
|
+
inset: 0;
|
|
139
|
+
pointer-events: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.pa-range__tick {
|
|
143
|
+
position: absolute;
|
|
144
|
+
top: 50%;
|
|
145
|
+
// --_pos is a 0–100% offset along the rail, set inline by JS. Logical
|
|
146
|
+
// inset + logical negative margin centres the mark, RTL-correct.
|
|
147
|
+
inset-inline-start: var(--_pos, 0%);
|
|
148
|
+
width: $range-tick-width;
|
|
149
|
+
height: $range-tick-minor-height;
|
|
150
|
+
margin-inline-start: calc(#{$range-tick-width} / -2);
|
|
151
|
+
transform: translateY(-50%);
|
|
152
|
+
border-radius: 999px;
|
|
153
|
+
background-color: var(--pa-range-tick, var(--pa-border-color));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.pa-range__tick--major {
|
|
157
|
+
width: $range-tick-major-width;
|
|
158
|
+
height: $range-tick-major-height;
|
|
159
|
+
margin-inline-start: calc(#{$range-tick-major-width} / -2);
|
|
160
|
+
background-color: var(--pa-range-tick-major, var(--pa-text-tertiary));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Labels (opt-in via data-tick-labels). Sit in a band below the track; JS
|
|
164
|
+
// adds .pa-range--ticks-labeled to the row to reserve the height.
|
|
165
|
+
.pa-range__tick-labels {
|
|
166
|
+
position: absolute;
|
|
167
|
+
inset-inline: 0;
|
|
168
|
+
top: calc(100% + #{$range-tick-label-gap});
|
|
169
|
+
height: 0;
|
|
170
|
+
pointer-events: none;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.pa-range__tick-label {
|
|
174
|
+
position: absolute;
|
|
175
|
+
inset-inline-start: var(--_pos, 0%);
|
|
176
|
+
transform: translateX(-50%); // symmetric box centring; position flips via inset
|
|
177
|
+
font-size: $font-size-2xs;
|
|
178
|
+
color: var(--pa-text-tertiary);
|
|
179
|
+
white-space: nowrap;
|
|
180
|
+
font-variant-numeric: tabular-nums;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.pa-range__thumb {
|
|
184
|
+
// Handle dimensions come from private props so the centring margin (and
|
|
185
|
+
// the shape modifiers below) stay in one place. Default = round handle.
|
|
186
|
+
--_thumb-w: var(--pa-range-thumb-size, #{$range-thumb-size});
|
|
187
|
+
--_thumb-h: var(--pa-range-thumb-size, #{$range-thumb-size});
|
|
188
|
+
|
|
189
|
+
position: absolute;
|
|
190
|
+
top: 50%;
|
|
191
|
+
// --_pos is the thumb centre as a 0–100% offset along the rail. Logical
|
|
192
|
+
// inset + a logical negative margin of half the handle keeps it centred
|
|
193
|
+
// and RTL-correct without any transform flip.
|
|
194
|
+
inset-inline-start: var(--_pos, 0%);
|
|
195
|
+
margin-inline-start: calc(var(--_thumb-w) / -2);
|
|
196
|
+
transform: translateY(-50%);
|
|
197
|
+
|
|
198
|
+
width: var(--_thumb-w);
|
|
199
|
+
height: var(--_thumb-h);
|
|
200
|
+
padding: 0;
|
|
201
|
+
border: $range-thumb-border solid var(--pa-range-thumb-border, var(--pa-accent));
|
|
202
|
+
border-radius: 50%;
|
|
203
|
+
background-color: var(--pa-range-thumb-bg, var(--pa-card-bg));
|
|
204
|
+
cursor: grab;
|
|
205
|
+
touch-action: none;
|
|
206
|
+
transition: box-shadow 0.15s ease, border-color 0.15s ease;
|
|
207
|
+
|
|
208
|
+
&:hover {
|
|
209
|
+
border-color: var(--pa-range-thumb-border-hover, var(--pa-accent-hover));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
&:focus-visible {
|
|
213
|
+
outline: none;
|
|
214
|
+
box-shadow: 0 0 0 0.3rem var(--pa-range-focus-ring, var(--pa-accent-light));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
&:active,
|
|
218
|
+
&.pa-range__thumb--grabbing {
|
|
219
|
+
cursor: grabbing;
|
|
220
|
+
box-shadow: 0 0 0 0.3rem var(--pa-range-focus-ring, var(--pa-accent-light));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* ---- Handle shapes ----
|
|
225
|
+
Modifiers on .pa-range that restyle its thumbs. Purely cosmetic — no JS
|
|
226
|
+
involvement — so a shape can be mixed per row. Each just overrides the
|
|
227
|
+
private size props + border-radius; centring follows automatically. */
|
|
228
|
+
|
|
229
|
+
// Rounded rectangle.
|
|
230
|
+
.pa-range--handle-rect .pa-range__thumb {
|
|
231
|
+
--_thumb-w: 1.2rem;
|
|
232
|
+
--_thumb-h: 1.9rem;
|
|
233
|
+
border-radius: var(--pa-border-radius-sm);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Thin vertical bar ("|") — solid accent, reads as a divider.
|
|
237
|
+
.pa-range--handle-bar .pa-range__thumb {
|
|
238
|
+
--_thumb-w: 0.35rem;
|
|
239
|
+
--_thumb-h: 2.2rem;
|
|
240
|
+
border-radius: 999px;
|
|
241
|
+
background-color: var(--pa-range-thumb-border, var(--pa-accent));
|
|
242
|
+
|
|
243
|
+
&:hover { background-color: var(--pa-range-thumb-border-hover, var(--pa-accent-hover)); }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Chevron handles ("‹ ›"). A rounded-rect grip with a CSS chevron drawn on
|
|
247
|
+
// top via ::before. Handles point outward (drag me to widen the range); the
|
|
248
|
+
// single-thumb variant keeps the max handle only, pointing at its bound.
|
|
249
|
+
// A 2-border corner only inks one half of its box, so after the 45° rotation
|
|
250
|
+
// the "›" sits toward its point. Nudge it back toward the handle centre by
|
|
251
|
+
// half its post-rotation reach (size / (2·√2)). The nudge is the OUTERMOST
|
|
252
|
+
// transform so it's a screen-space shift, unaffected by the inner rotate.
|
|
253
|
+
$range-chevron-size: 0.5rem;
|
|
254
|
+
$range-chevron-nudge: $range-chevron-size * 0.354; // ≈ size / (2√2)
|
|
255
|
+
|
|
256
|
+
.pa-range--handle-arrow .pa-range__thumb {
|
|
257
|
+
--_thumb-w: 1.6rem;
|
|
258
|
+
--_thumb-h: 2rem;
|
|
259
|
+
border-radius: var(--pa-border-radius-sm);
|
|
260
|
+
|
|
261
|
+
&::before {
|
|
262
|
+
content: '';
|
|
263
|
+
position: absolute;
|
|
264
|
+
top: 50%;
|
|
265
|
+
inset-inline-start: 50%;
|
|
266
|
+
width: $range-chevron-size;
|
|
267
|
+
height: $range-chevron-size;
|
|
268
|
+
// top+end borders rotated 45° form a "›" (points toward inline-end).
|
|
269
|
+
border-block-start: $range-thumb-border solid var(--pa-range-thumb-border, var(--pa-accent));
|
|
270
|
+
border-inline-end: $range-thumb-border solid var(--pa-range-thumb-border, var(--pa-accent));
|
|
271
|
+
transform: translateX(#{-$range-chevron-nudge}) translate(-50%, -50%) rotate(45deg);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Min handle points the other way ("‹") — nudge the opposite direction.
|
|
275
|
+
.pa-range--handle-arrow .pa-range__thumb--min::before {
|
|
276
|
+
transform: translateX(#{$range-chevron-nudge}) translate(-50%, -50%) rotate(-135deg);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Needle ("▽") — a downward triangle balancing its point on the track, like a
|
|
280
|
+
// gauge needle marking the value. The thumb box goes invisible (it stays as the
|
|
281
|
+
// grab/focus target, full row height) and a filled triangle is drawn via
|
|
282
|
+
// ::before with clip-path — apex at the track centre, body rising above. Pure
|
|
283
|
+
// CSS, no glyph/SVG, matching the other shapes.
|
|
284
|
+
$range-needle-width: 1.2rem;
|
|
285
|
+
$range-needle-height: 1.1rem; // fits above the track centre within the row
|
|
286
|
+
|
|
287
|
+
.pa-range--handle-needle .pa-range__thumb {
|
|
288
|
+
--_thumb-w: #{$range-needle-width};
|
|
289
|
+
--_thumb-h: #{$range-thumb-tall};
|
|
290
|
+
background-color: transparent;
|
|
291
|
+
border-color: transparent;
|
|
292
|
+
border-radius: var(--pa-border-radius-sm); // shapes the focus halo only
|
|
293
|
+
|
|
294
|
+
&::before {
|
|
295
|
+
content: '';
|
|
296
|
+
position: absolute;
|
|
297
|
+
inset-inline-start: 50%;
|
|
298
|
+
bottom: 50%; // apex rests on the track centre
|
|
299
|
+
width: $range-needle-width;
|
|
300
|
+
height: $range-needle-height;
|
|
301
|
+
margin-inline-start: calc(#{$range-needle-width} / -2);
|
|
302
|
+
background-color: var(--pa-range-thumb-border, var(--pa-accent));
|
|
303
|
+
clip-path: polygon(50% 100%, 0 0, 100% 0); // flat top, point down
|
|
304
|
+
transition: background-color 0.15s ease;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
&:hover::before {
|
|
308
|
+
background-color: var(--pa-range-thumb-border-hover, var(--pa-accent-hover));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Single-thumb variant — hide the unused min thumb. JS reads the modifier
|
|
313
|
+
// to decide how many handles to wire up and how the fill is anchored.
|
|
314
|
+
.pa-range--single {
|
|
315
|
+
.pa-range__thumb--min {
|
|
316
|
+
display: none;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.pa-range--disabled {
|
|
321
|
+
opacity: 0.5;
|
|
322
|
+
pointer-events: none;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* ----------------------------------------
|
|
326
|
+
Compact control — pa-range-group
|
|
327
|
+
---------------------------------------- */
|
|
328
|
+
|
|
329
|
+
$range-group-toggle-padding-v: $spacing-sm;
|
|
330
|
+
$range-group-toggle-padding-h: $spacing-md;
|
|
331
|
+
$range-group-panel-min-width: 32rem;
|
|
332
|
+
$range-group-panel-padding: $spacing-base;
|
|
333
|
+
$range-group-panel-row-gap: $spacing-lg;
|
|
334
|
+
|
|
335
|
+
.pa-range-group {
|
|
336
|
+
position: relative;
|
|
337
|
+
display: inline-flex;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.pa-range-group__toggle {
|
|
341
|
+
display: inline-flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
gap: $spacing-md;
|
|
344
|
+
width: 100%;
|
|
345
|
+
padding: $range-group-toggle-padding-v $range-group-toggle-padding-h;
|
|
346
|
+
border: $border-width-base solid var(--pa-border-color);
|
|
347
|
+
border-radius: var(--pa-border-radius);
|
|
348
|
+
background-color: var(--pa-input-bg);
|
|
349
|
+
color: var(--pa-text-color-1);
|
|
350
|
+
font-size: $font-size-sm;
|
|
351
|
+
line-height: 1.3;
|
|
352
|
+
text-align: start;
|
|
353
|
+
cursor: pointer;
|
|
354
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
355
|
+
|
|
356
|
+
&:hover {
|
|
357
|
+
border-color: var(--pa-accent-hover);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
&:focus-visible {
|
|
361
|
+
outline: none;
|
|
362
|
+
border-color: var(--pa-accent);
|
|
363
|
+
box-shadow: 0 0 0 0.3rem var(--pa-accent-light);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Single line: "LABEL value / LABEL value / …". Keeps the toggle the same
|
|
368
|
+
// height as sibling inputs; truncates with an ellipsis when it runs out of
|
|
369
|
+
// room. JS builds the segments (see updateSummary in range-group.js).
|
|
370
|
+
.pa-range-group__summary {
|
|
371
|
+
flex: 1;
|
|
372
|
+
min-width: 0;
|
|
373
|
+
font-size: $font-size-sm;
|
|
374
|
+
white-space: nowrap;
|
|
375
|
+
overflow: hidden;
|
|
376
|
+
text-overflow: ellipsis;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// One dimension's "LABEL value" pair.
|
|
380
|
+
.pa-range-group__seg-label {
|
|
381
|
+
font-size: $font-size-2xs;
|
|
382
|
+
font-weight: $font-weight-semibold;
|
|
383
|
+
letter-spacing: 0.02em;
|
|
384
|
+
text-transform: uppercase;
|
|
385
|
+
color: var(--pa-text-tertiary);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.pa-range-group__seg-value {
|
|
389
|
+
font-weight: $font-weight-medium;
|
|
390
|
+
color: var(--pa-text-color-1);
|
|
391
|
+
font-variant-numeric: tabular-nums;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// "Any" placeholder value — muted so active dimensions read as the signal.
|
|
395
|
+
.pa-range-group__seg-value--empty {
|
|
396
|
+
color: var(--pa-text-tertiary);
|
|
397
|
+
font-weight: $font-weight-normal;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// The " / " separators between segments.
|
|
401
|
+
.pa-range-group__seg-sep {
|
|
402
|
+
color: var(--pa-text-tertiary);
|
|
403
|
+
margin-inline: 0.4rem;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.pa-range-group__caret {
|
|
407
|
+
flex-shrink: 0;
|
|
408
|
+
font-size: $font-size-xs;
|
|
409
|
+
color: var(--pa-text-secondary);
|
|
410
|
+
transition: transform 0.2s ease;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.pa-range-group--open .pa-range-group__caret {
|
|
414
|
+
transform: rotate(180deg);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* ---- Floating panel ---- */
|
|
418
|
+
|
|
419
|
+
.pa-range-group__panel {
|
|
420
|
+
display: none;
|
|
421
|
+
flex-direction: column;
|
|
422
|
+
gap: $range-group-panel-row-gap;
|
|
423
|
+
min-width: var(--pa-range-group-panel-min-width, #{$range-group-panel-min-width});
|
|
424
|
+
padding: $range-group-panel-padding;
|
|
425
|
+
border: $border-width-base solid var(--pa-border-color);
|
|
426
|
+
border-radius: var(--pa-border-radius-lg);
|
|
427
|
+
background-color: var(--pa-card-bg);
|
|
428
|
+
box-shadow: var(--pa-detail-shadow);
|
|
429
|
+
z-index: $z-index-dropdown;
|
|
430
|
+
|
|
431
|
+
&.pa-range-group__panel--open {
|
|
432
|
+
display: flex;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.pa-range-group__row {
|
|
437
|
+
display: flex;
|
|
438
|
+
flex-direction: column;
|
|
439
|
+
gap: $spacing-sm;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.pa-range-group__row-head {
|
|
443
|
+
display: flex;
|
|
444
|
+
align-items: baseline;
|
|
445
|
+
justify-content: space-between;
|
|
446
|
+
gap: $spacing-sm;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.pa-range-group__row-label {
|
|
450
|
+
font-size: $font-size-sm;
|
|
451
|
+
font-weight: $font-weight-semibold;
|
|
452
|
+
color: var(--pa-text-color-1);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.pa-range-group__row-value {
|
|
456
|
+
font-size: $font-size-sm;
|
|
457
|
+
font-weight: $font-weight-medium;
|
|
458
|
+
font-variant-numeric: tabular-nums;
|
|
459
|
+
// Tracks the fill colour so retinting the slider retints its readout too.
|
|
460
|
+
color: var(--pa-range-fill, var(--pa-accent));
|
|
461
|
+
|
|
462
|
+
&.pa-range-group__row-value--empty {
|
|
463
|
+
color: var(--pa-text-tertiary);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.pa-range-group__actions {
|
|
468
|
+
display: flex;
|
|
469
|
+
justify-content: flex-end;
|
|
470
|
+
gap: $spacing-sm;
|
|
471
|
+
margin-top: $spacing-xs;
|
|
472
|
+
padding-top: $range-group-panel-row-gap;
|
|
473
|
+
border-top: $border-width-base solid var(--pa-border-color);
|
|
474
|
+
}
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
padding: $spacing-lg;
|
|
119
119
|
min-height: $stat-square-min-size;
|
|
120
120
|
min-width: $stat-square-min-size;
|
|
121
|
+
max-height: $stat-square-max-height; // keep full-width tiles from growing into page-dominating banners
|
|
121
122
|
position: relative;
|
|
122
123
|
border-radius: var(--pa-border-radius);
|
|
123
124
|
box-shadow: $shadow-md;
|
|
@@ -183,6 +184,17 @@
|
|
|
183
184
|
&.pa-stat--warning {
|
|
184
185
|
background-color: var(--pa-warning-bg);
|
|
185
186
|
color: var(--pa-btn-warning-text);
|
|
187
|
+
|
|
188
|
+
// Warning is the one square variant with DARK text (on a light amber
|
|
189
|
+
// surface). The number's default black text-shadow + drop-shadow stack is
|
|
190
|
+
// tuned for the white-text variants; against a dark glyph it has no
|
|
191
|
+
// contrast and smears into an "ink-blot" halo. Dial it to a whisper and
|
|
192
|
+
// drop the costly drop-shadow layer. Covers classic AND fit mode, since
|
|
193
|
+
// both render the number through .pa-stat__number.
|
|
194
|
+
.pa-stat__number {
|
|
195
|
+
text-shadow: 0 1px 1px rgba(0, 0, 0, $opacity-light);
|
|
196
|
+
filter: none;
|
|
197
|
+
}
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
&.pa-stat--danger {
|
|
@@ -194,6 +206,157 @@
|
|
|
194
206
|
background-color: var(--pa-text-color-2);
|
|
195
207
|
color: var(--pa-btn-primary-text);
|
|
196
208
|
}
|
|
209
|
+
|
|
210
|
+
// ----------------------------------------------------------------
|
|
211
|
+
// FIT + PROGRESSIVE DISCLOSURE mode (opt-in: [data-pa-stat-fit])
|
|
212
|
+
// The number is sized to fill the tile by pa-stat-fit.js, which also
|
|
213
|
+
// wraps __number + __symbol into a __slot > __group at runtime. Lower-
|
|
214
|
+
// priority rows (label → change → context) reveal as the tile earns
|
|
215
|
+
// both width and height.
|
|
216
|
+
//
|
|
217
|
+
// REQUIRES A HEIGHT SOURCE on the tile (grid cell with a set height,
|
|
218
|
+
// explicit height, or aspect-ratio): container-type: size makes the
|
|
219
|
+
// box size independently of its content, so without a height it falls
|
|
220
|
+
// back to the $stat-square-min-size floor.
|
|
221
|
+
// ----------------------------------------------------------------
|
|
222
|
+
&[data-pa-stat-fit] {
|
|
223
|
+
container-type: size;
|
|
224
|
+
container-name: #{$stat-square-fit-container-name};
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
flex-wrap: nowrap;
|
|
227
|
+
align-content: stretch;
|
|
228
|
+
align-items: stretch;
|
|
229
|
+
max-height: none; // fit height is container-driven; undo the base cap.
|
|
230
|
+
// min-height (the base $stat-square-min-size) stays as
|
|
231
|
+
// the default height when none is set + collapse floor.
|
|
232
|
+
|
|
233
|
+
// __slot grows to take the room not used by the metadata rows; the
|
|
234
|
+
// number is fit into it. ResizeObserver watches this element.
|
|
235
|
+
.pa-stat__slot {
|
|
236
|
+
flex: 1 1 auto;
|
|
237
|
+
min-height: 0;
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
justify-content: flex-start;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// font-size is set inline (px) on __group by the JS; children scale via em.
|
|
244
|
+
.pa-stat__group {
|
|
245
|
+
display: inline-flex;
|
|
246
|
+
align-items: baseline;
|
|
247
|
+
column-gap: $stat-square-symbol-gap;
|
|
248
|
+
line-height: 1;
|
|
249
|
+
white-space: nowrap;
|
|
250
|
+
font-weight: $font-weight-bold;
|
|
251
|
+
|
|
252
|
+
.pa-stat__number { font-size: 1em; }
|
|
253
|
+
.pa-stat__symbol {
|
|
254
|
+
font-size: #{$stat-square-fit-symbol-ratio}em;
|
|
255
|
+
opacity: $stat-symbol-opacity;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// pa-stat-fit.js wraps the metadata rows into this column so the whole
|
|
260
|
+
// group can move beside the number in the horizontal (banner) layout.
|
|
261
|
+
// The number is the HERO; the metadata is subordinate — pa-stat-fit.js sets
|
|
262
|
+
// this column's font-size to a small fraction of the fitted number size
|
|
263
|
+
// (META_RATIO), so the rows below stay "supersmall" relative to the number at
|
|
264
|
+
// every tile size. The rows are em-relative so that one font-size cascades to
|
|
265
|
+
// all three. The value here is only the no-JS fallback.
|
|
266
|
+
.pa-stat__meta {
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: column;
|
|
269
|
+
min-width: 0; // let children ellipsis instead of overflowing
|
|
270
|
+
flex: 0 0 auto; // vertical layout: natural height below the slot
|
|
271
|
+
font-size: $stat-square-fit-meta-size; // JS overrides inline (px); this is the fallback
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// metadata rows sit below the slot, each on its own line — all em-relative to
|
|
275
|
+
// __meta's (JS-driven) font-size so they scale with the hero number.
|
|
276
|
+
.pa-stat__label {
|
|
277
|
+
flex: 0 0 auto; // undo the base square's flex-basis:100%
|
|
278
|
+
margin-top: 0.35em;
|
|
279
|
+
font-size: 1em; // the meta base — the largest metadata line
|
|
280
|
+
}
|
|
281
|
+
.pa-stat__change {
|
|
282
|
+
flex: 0 0 auto;
|
|
283
|
+
margin-top: 0.35em;
|
|
284
|
+
font-size: 0.9em;
|
|
285
|
+
font-weight: $font-weight-semibold;
|
|
286
|
+
line-height: 1.2;
|
|
287
|
+
color: inherit; // default to the tile's text colour…
|
|
288
|
+
white-space: nowrap;
|
|
289
|
+
overflow: hidden;
|
|
290
|
+
text-overflow: ellipsis;
|
|
291
|
+
|
|
292
|
+
// …opt-in sentiment tints (same scale as the hero variant's __change)
|
|
293
|
+
&--positive { color: var(--pa-positive); }
|
|
294
|
+
&--neutral { color: var(--pa-neutral); }
|
|
295
|
+
&--negative { color: var(--pa-negative); }
|
|
296
|
+
}
|
|
297
|
+
.pa-stat__context {
|
|
298
|
+
flex: 0 0 auto;
|
|
299
|
+
margin-top: 0.35em;
|
|
300
|
+
font-size: 0.8em;
|
|
301
|
+
opacity: 0.7;
|
|
302
|
+
white-space: nowrap;
|
|
303
|
+
overflow: hidden;
|
|
304
|
+
text-overflow: ellipsis;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---- priority ladder: P0 number always; reveal P1–P4 with space ----
|
|
308
|
+
.pa-stat__symbol,
|
|
309
|
+
.pa-stat__label,
|
|
310
|
+
.pa-stat__change,
|
|
311
|
+
.pa-stat__context { display: none; }
|
|
312
|
+
|
|
313
|
+
@container #{$stat-square-fit-container-name} (min-width: #{$stat-square-fit-symbol-min-w}) and (min-height: #{$stat-square-fit-symbol-min-h}) {
|
|
314
|
+
.pa-stat__symbol { display: inline; }
|
|
315
|
+
}
|
|
316
|
+
@container #{$stat-square-fit-container-name} (min-width: #{$stat-square-fit-label-min-w}) and (min-height: #{$stat-square-fit-label-min-h}) {
|
|
317
|
+
.pa-stat__label { display: block; }
|
|
318
|
+
}
|
|
319
|
+
@container #{$stat-square-fit-container-name} (min-width: #{$stat-square-fit-change-min-w}) and (min-height: #{$stat-square-fit-change-min-h}) {
|
|
320
|
+
.pa-stat__change { display: block; }
|
|
321
|
+
}
|
|
322
|
+
@container #{$stat-square-fit-container-name} (min-width: #{$stat-square-fit-context-min-w}) and (min-height: #{$stat-square-fit-context-min-h}) {
|
|
323
|
+
.pa-stat__context { display: block; }
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ---- HORIZONTAL "banner" layout for wide tiles ----
|
|
327
|
+
// Toggled by pa-stat-fit.js (.pa-stat--fit-wide) when the tile is at least
|
|
328
|
+
// $stat-square-fit-horizontal-min-w wide. This CANNOT be a `@container`
|
|
329
|
+
// query: the tile is its own size container, and a container query only
|
|
330
|
+
// restyles a container's DESCENDANTS — never the container element itself,
|
|
331
|
+
// so the tile's own `flex-direction` would never flip. A JS class on the
|
|
332
|
+
// tile sidesteps that.
|
|
333
|
+
//
|
|
334
|
+
// Number on the LEFT (__slot is first in DOM → first flex item), filling
|
|
335
|
+
// the height so it stays big; the metadata column sits to its RIGHT,
|
|
336
|
+
// vertically centred. All meta rows are revealed here regardless of height
|
|
337
|
+
// — stacked beside the number they don't need vertical room — so a short
|
|
338
|
+
// wide banner still shows trend + context.
|
|
339
|
+
&.pa-stat--fit-wide {
|
|
340
|
+
flex-direction: row;
|
|
341
|
+
align-items: stretch;
|
|
342
|
+
column-gap: $spacing-lg;
|
|
343
|
+
|
|
344
|
+
.pa-stat__slot {
|
|
345
|
+
flex: 0 0 #{$stat-square-fit-number-width};
|
|
346
|
+
min-width: 0; // never let the number push past its column
|
|
347
|
+
justify-content: center; // centre the number within its column
|
|
348
|
+
}
|
|
349
|
+
.pa-stat__meta {
|
|
350
|
+
flex: 1 1 auto;
|
|
351
|
+
min-width: 0; // allow the rows to ellipsis
|
|
352
|
+
justify-content: center; // centre the rows vertically beside the number
|
|
353
|
+
}
|
|
354
|
+
.pa-stat__symbol { display: inline; }
|
|
355
|
+
.pa-stat__label,
|
|
356
|
+
.pa-stat__change,
|
|
357
|
+
.pa-stat__context { display: block; }
|
|
358
|
+
}
|
|
359
|
+
}
|
|
197
360
|
}
|
|
198
361
|
}
|
|
199
362
|
|