@keenmate/pure-admin-core 2.7.0 → 2.8.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,223 @@
1
+ /* ========================================
2
+ KPI · Sparkline list
3
+ Each KPI is one row: label · sparkline · value · Δ%. Built for vertical
4
+ scanning rather than per-tile depth — no view-mode toggle, no status
5
+ pills. Container queries collapse 4-col → 2-row → 3-row as the card
6
+ narrows.
7
+
8
+ Row template uses local SCSS variables for the four column widths
9
+ (`$spark-col-label`, `$spark-col-chart`, `$spark-col-value`,
10
+ `$spark-col-delta`) so all responsive overrides reference the same
11
+ widths — change one and every breakpoint follows. The `--no-delta`
12
+ modifier drops the rightmost column; the other three are load-bearing
13
+ (a sparkline list without label, chart, or value is a different
14
+ design).
15
+ ======================================== */
16
+ @use '../variables' as *;
17
+
18
+ /* Per-column track widths — referenced from every template selector. */
19
+ $spark-col-label: minmax(14rem, 28%);
20
+ $spark-col-chart: minmax(10rem, 1fr);
21
+ $spark-col-value: minmax(8rem, 18%);
22
+ $spark-col-delta: minmax(7rem, 12%);
23
+
24
+ /* Card is a query container so rows react to *card* width, not viewport. */
25
+ .pa-kpi-spark-list {
26
+ container-type: inline-size;
27
+ }
28
+ .pa-kpi-spark-list__body { padding: 0; }
29
+
30
+ /* ----- Row: wide 4-column grid (label · chart · value · delta) ---------- */
31
+ .pa-kpi-spark-row {
32
+ display: grid;
33
+ grid-template-columns:
34
+ $spark-col-label
35
+ $spark-col-chart
36
+ $spark-col-value
37
+ $spark-col-delta;
38
+ align-items: center;
39
+ gap: 1.6rem;
40
+ padding: 1.4rem 2rem;
41
+ border-bottom: 1px solid var(--pa-border-color);
42
+
43
+ &:last-child { border-bottom: 0; }
44
+ }
45
+
46
+ /* Modifier: --no-delta — drops the rightmost column. label · chart ·
47
+ value only. Useful when the chart's slope already conveys direction
48
+ and the delta would be redundant. Hides the delta element and shrinks
49
+ the row template to 3 columns. */
50
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row {
51
+ grid-template-columns:
52
+ $spark-col-label
53
+ $spark-col-chart
54
+ $spark-col-value;
55
+ }
56
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row__delta { display: none; }
57
+
58
+ /* Mid-narrow card (1×3 page-grid + 45% column): stack to 2 rows.
59
+ Label/value/delta on top, full-width chart below. Default order is
60
+ value-above-chart; use .pa-kpi-spark-list--chart-first to flip. */
61
+ @container (max-width: 640px) {
62
+ .pa-kpi-spark-row {
63
+ grid-template-columns: minmax(0, 1fr) auto auto;
64
+ grid-template-rows: auto auto;
65
+ grid-template-areas:
66
+ "label value delta"
67
+ "chart chart chart";
68
+ column-gap: 1.2rem;
69
+ row-gap: 0.4rem;
70
+ align-items: baseline;
71
+ padding: 1.2rem 1.6rem;
72
+ }
73
+ .pa-kpi-spark-row__label { grid-area: label; align-self: center; font-size: 1.25rem; }
74
+ .pa-kpi-spark-row__value { grid-area: value; }
75
+ .pa-kpi-spark-row__delta { grid-area: delta; font-size: 1.25rem; }
76
+ .pa-kpi-spark-row__chart { grid-area: chart; }
77
+ .pa-kpi-spark-row__num { font-size: 2rem; }
78
+
79
+ /* --chart-first: rotates the canonical L→R order 90°. Label on top,
80
+ chart in the middle, value+delta side-by-side at the bottom. */
81
+ .pa-kpi-spark-list--chart-first .pa-kpi-spark-row {
82
+ grid-template-columns: 1fr auto;
83
+ grid-template-rows: auto auto auto;
84
+ grid-template-areas:
85
+ "label label"
86
+ "chart chart"
87
+ "value delta";
88
+ row-gap: 0.5rem;
89
+ column-gap: 1rem;
90
+ padding: 1.2rem 1.6rem;
91
+ }
92
+ .pa-kpi-spark-list--chart-first .pa-kpi-spark-row__label { align-self: start; }
93
+ .pa-kpi-spark-list--chart-first .pa-kpi-spark-row__value { text-align: start; align-self: baseline; }
94
+ .pa-kpi-spark-list--chart-first .pa-kpi-spark-row__delta { align-self: baseline; }
95
+
96
+ /* --no-delta at mid-narrow: 2-row layout, top row is label+value only. */
97
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row {
98
+ grid-template-columns: minmax(0, 1fr) auto;
99
+ grid-template-areas:
100
+ "label value"
101
+ "chart chart";
102
+ }
103
+
104
+ /* --no-delta + --chart-first: 3-row single-column stack. */
105
+ .pa-kpi-spark-list--no-delta.pa-kpi-spark-list--chart-first .pa-kpi-spark-row {
106
+ grid-template-columns: 1fr;
107
+ grid-template-areas:
108
+ "label"
109
+ "chart"
110
+ "value";
111
+ }
112
+ }
113
+
114
+ /* Very narrow card (~280–360px, 25% page-grid stress test): force the
115
+ chart-first 3-row layout regardless of modifier. */
116
+ @container (max-width: 360px) {
117
+ .pa-kpi-spark-row {
118
+ grid-template-columns: 1fr auto;
119
+ grid-template-rows: auto auto auto;
120
+ grid-template-areas:
121
+ "label label"
122
+ "chart chart"
123
+ "value delta";
124
+ row-gap: 0.5rem;
125
+ column-gap: 1rem;
126
+ padding: 1.2rem 1.4rem;
127
+ }
128
+ .pa-kpi-spark-row__label { grid-area: label; align-self: start; }
129
+ .pa-kpi-spark-row__chart { grid-area: chart; }
130
+ .pa-kpi-spark-row__value { grid-area: value; text-align: start; align-self: baseline; }
131
+ .pa-kpi-spark-row__delta { grid-area: delta; align-self: baseline; }
132
+
133
+ /* --no-delta at very-narrow: 3-row single-column stack (no delta cell). */
134
+ .pa-kpi-spark-list--no-delta .pa-kpi-spark-row {
135
+ grid-template-columns: 1fr;
136
+ grid-template-areas:
137
+ "label"
138
+ "chart"
139
+ "value";
140
+ }
141
+ }
142
+
143
+ /* ----- Cell typography -------------------------------------------------- */
144
+ .pa-kpi-spark-row__label {
145
+ font-family: var(--base-font-family-mono);
146
+ font-size: 1.4rem;
147
+ font-weight: 700;
148
+ letter-spacing: 0.1em;
149
+ text-transform: uppercase;
150
+ color: color-mix(in srgb, var(--pa-text-color-1) 60%, transparent);
151
+ }
152
+
153
+ /* Sparkline cell — line + filled area + trailing dot (the dot is rendered
154
+ as a CSS span by the JS init pass; see kpi-showcases.js). */
155
+ .pa-kpi-spark-row__chart {
156
+ position: relative; /* anchor for .pa-kpi-spark-dot */
157
+
158
+ svg {
159
+ display: block;
160
+ width: 100%;
161
+ height: var(--pa-chart-trendline-height);
162
+ overflow: visible;
163
+ }
164
+
165
+ polyline {
166
+ fill: none;
167
+ stroke: currentColor;
168
+ stroke-width: var(--pa-chart-trendline-stroke);
169
+ stroke-linecap: round;
170
+ stroke-linejoin: round;
171
+ }
172
+
173
+ polygon {
174
+ fill: currentColor;
175
+ fill-opacity: 0.18; /* same hue as line, soft area shading */
176
+ stroke: none;
177
+ }
178
+ }
179
+
180
+ /* Sentiment-coloured sparkline. Color is set on the chart wrapper so
181
+ currentColor resolves for both the SVG content (line/area) and the
182
+ dot inside the wrapper. */
183
+ .pa-kpi-spark-row {
184
+ &--up-strong .pa-kpi-spark-row__chart { color: var(--pa-very-positive); }
185
+ &--up .pa-kpi-spark-row__chart { color: var(--pa-positive); }
186
+ &--flat .pa-kpi-spark-row__chart { color: var(--pa-neutral); }
187
+ &--down .pa-kpi-spark-row__chart { color: var(--pa-negative); }
188
+ &--down-strong .pa-kpi-spark-row__chart { color: var(--pa-very-negative); }
189
+ }
190
+
191
+ /* ----- Value (focal white number + muted unit) ------------------------- */
192
+ .pa-kpi-spark-row__value {
193
+ font-family: var(--base-font-family-mono);
194
+ text-align: end;
195
+ line-height: 1;
196
+ }
197
+ .pa-kpi-spark-row__num {
198
+ font-size: 2.6rem;
199
+ font-weight: 700;
200
+ letter-spacing: -0.02em;
201
+ color: var(--pa-text-color-1);
202
+ }
203
+ .pa-kpi-spark-row__unit {
204
+ font-size: 1.3rem;
205
+ font-weight: 500;
206
+ color: var(--pa-text-secondary);
207
+ margin-left: 0.2rem;
208
+ }
209
+
210
+ /* ----- Delta (sentiment-coloured) -------------------------------------- */
211
+ .pa-kpi-spark-row__delta {
212
+ font-family: var(--base-font-family-mono);
213
+ font-size: 1.4rem;
214
+ font-weight: 600;
215
+ text-align: end;
216
+ font-variant-numeric: tabular-nums;
217
+
218
+ &--very-positive { color: var(--pa-very-positive); }
219
+ &--positive { color: var(--pa-positive); }
220
+ &--neutral { color: var(--pa-neutral); }
221
+ &--negative { color: var(--pa-negative); }
222
+ &--very-negative { color: var(--pa-very-negative); }
223
+ }
@@ -0,0 +1,236 @@
1
+ /* ========================================
2
+ KPI · Terminal grid
3
+ Bloomberg-style dense panel: mono numbers, status pills, inline SVG
4
+ sparklines, ▲▼ deltas, optional segmented tab strip for swapping
5
+ the body content (different tile sets / grids per tab).
6
+ Shared chrome (header, live, footer, detail popover, spark-dot) is in
7
+ _kpi-base.scss.
8
+ ======================================== */
9
+ @use '../variables' as *;
10
+
11
+ /* ----- Tab strip (segmented button group) ------------------------------
12
+ Generalised tabs — each `.pa-kpi-terminal__tab` carries a `data-tab`
13
+ slug that matches a `.pa-kpi-terminal__pane[data-tab="…"]` in the body.
14
+ JS toggles `.is-active` on the clicked tab + matching pane; tabs and
15
+ panes are otherwise independent so each pane can hold a completely
16
+ different grid (different tile count, different grid modifier). */
17
+ .pa-kpi-terminal__controls {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: 1.6rem;
21
+ }
22
+ .pa-kpi-terminal__tabs {
23
+ display: inline-flex;
24
+ border: 1px solid var(--pa-border-color);
25
+ border-radius: 0.4rem;
26
+ overflow: hidden;
27
+ }
28
+ .pa-kpi-terminal__tab {
29
+ background: transparent;
30
+ border: 0;
31
+ color: var(--pa-text-color-2);
32
+ padding: 0.4rem 1.1rem;
33
+ font-family: var(--base-font-family-mono);
34
+ font-size: 1.1rem;
35
+ font-weight: 600;
36
+ letter-spacing: 0.06em;
37
+ cursor: pointer;
38
+ transition: background-color 0.1s ease-out, color 0.1s ease-out;
39
+
40
+ &:hover {
41
+ color: var(--pa-text-color-1);
42
+ }
43
+
44
+ &.is-active {
45
+ background: var(--pa-text-color-1);
46
+ color: var(--pa-card-bg);
47
+ }
48
+ }
49
+
50
+ /* ----- Pane visibility ------------------------------------------------- */
51
+ .pa-kpi-terminal__pane {
52
+ display: none;
53
+
54
+ &.is-active { display: block; }
55
+ }
56
+
57
+ /* ----- Grid + tile borders ----------------------------------------------
58
+ Hairline 1px borders between tiles, no gap. Last-row/last-column
59
+ borders suppressed via :nth-last-child / :nth-child selectors. */
60
+ .pa-kpi-terminal__body {
61
+ padding: 0;
62
+ }
63
+ .pa-kpi-terminal__grid {
64
+ display: grid;
65
+ grid-template-columns: repeat(2, 1fr);
66
+
67
+ .pa-kpi-tile {
68
+ border-bottom: 1px solid var(--pa-border-color);
69
+ border-right: 1px solid var(--pa-border-color);
70
+ }
71
+ }
72
+ .pa-kpi-terminal__grid--2col {
73
+ .pa-kpi-tile:nth-child(2n) { border-right: 0; }
74
+ .pa-kpi-tile:nth-last-child(-n+2) { border-bottom: 0; }
75
+ }
76
+
77
+ /* ----- Tile (per-KPI panel) --------------------------------------------- */
78
+ .pa-kpi-tile {
79
+ position: relative;
80
+ padding: 1.4rem 1.8rem 1.6rem;
81
+ min-height: 16rem;
82
+ display: flex;
83
+ flex-direction: column;
84
+
85
+ /* Standalone modifier: tile lives directly inside a .pa-col-* (no
86
+ neighbour cells in a parent grid) — gets a full border + card bg so
87
+ it doesn't look orphaned. */
88
+ &--standalone {
89
+ background: var(--pa-card-bg);
90
+ border: 1px solid var(--pa-border-color);
91
+ margin-bottom: 1.2rem;
92
+
93
+ &:last-child { margin-bottom: 0; }
94
+ }
95
+ }
96
+
97
+ /* ----- Tile head: ID · period + status pill ----------------------------- */
98
+ .pa-kpi-tile__head {
99
+ display: flex;
100
+ justify-content: space-between;
101
+ align-items: center;
102
+ font-family: var(--base-font-family-mono);
103
+ font-size: 1.3rem;
104
+ letter-spacing: 0.04em;
105
+ margin-bottom: 0.3rem;
106
+ }
107
+ .pa-kpi-tile__id {
108
+ color: var(--pa-text-tertiary);
109
+ font-weight: 600;
110
+ }
111
+ .pa-kpi-tile__status {
112
+ font-family: var(--base-font-family-mono);
113
+ font-size: 1.2rem;
114
+ font-weight: 700;
115
+ letter-spacing: 0.08em;
116
+ padding: 0.3rem 0.9rem;
117
+ line-height: 1.3;
118
+
119
+ &--warn {
120
+ background: var(--pa-warning-bg);
121
+ color: var(--pa-btn-warning-text);
122
+ }
123
+
124
+ /* GOOD is the "default" state — text-only, no chrome */
125
+ &--good {
126
+ background: transparent;
127
+ color: var(--pa-text-color-1);
128
+ padding: 0.2rem 0;
129
+ }
130
+
131
+ &--neutral {
132
+ background: color-mix(in srgb, var(--pa-text-color-2) 25%, transparent);
133
+ color: var(--pa-text-color-1);
134
+ }
135
+ }
136
+
137
+ /* ----- Label ------------------------------------------------------------ */
138
+ .pa-kpi-tile__label {
139
+ font-family: var(--base-font-family-mono);
140
+ font-size: 1.4rem;
141
+ font-weight: 700;
142
+ letter-spacing: 0.1em;
143
+ text-transform: uppercase;
144
+ color: var(--pa-text-strong);
145
+ margin-bottom: 0.8rem;
146
+ }
147
+
148
+ /* ----- Big value -------------------------------------------------------
149
+ One value per tile — sentiment colour set via `--very-positive` /
150
+ `--positive` / `--neutral` / `--negative` / `--very-negative` modifier
151
+ on the value element. */
152
+ .pa-kpi-tile__values {
153
+ margin-bottom: 0.4rem;
154
+ }
155
+ .pa-kpi-tile__value {
156
+ display: inline-flex;
157
+ align-items: baseline;
158
+ gap: 0.3rem;
159
+ font-family: var(--base-font-family-mono);
160
+ line-height: 1;
161
+
162
+ &--very-positive .pa-kpi-tile__num { color: var(--pa-very-positive); }
163
+ &--positive .pa-kpi-tile__num { color: var(--pa-positive); }
164
+ &--neutral .pa-kpi-tile__num { color: var(--pa-neutral); }
165
+ &--negative .pa-kpi-tile__num { color: var(--pa-negative); }
166
+ &--very-negative .pa-kpi-tile__num { color: var(--pa-very-negative); }
167
+ }
168
+
169
+ .pa-kpi-tile__num {
170
+ font-size: 3.8rem;
171
+ font-weight: 700;
172
+ letter-spacing: -0.02em;
173
+ color: var(--pa-text-color-1);
174
+ }
175
+ .pa-kpi-tile__unit {
176
+ font-size: 1.6rem;
177
+ font-weight: 500;
178
+ color: var(--pa-text-secondary);
179
+ }
180
+
181
+ /* ----- Previous-value + delta row -------------------------------------- */
182
+ .pa-kpi-tile__prev {
183
+ display: flex;
184
+ justify-content: space-between;
185
+ align-items: center;
186
+ font-family: var(--base-font-family-mono);
187
+ font-size: 1.3rem;
188
+ margin-top: auto; /* push to bottom of flex column */
189
+ margin-bottom: 0.4rem;
190
+ color: var(--pa-text-tertiary);
191
+ }
192
+ .pa-kpi-tile__delta {
193
+ &--very-positive { color: var(--pa-very-positive); }
194
+ &--positive { color: var(--pa-positive); }
195
+ &--neutral { color: var(--pa-neutral); }
196
+ &--negative { color: var(--pa-negative); }
197
+ &--very-negative { color: var(--pa-very-negative); }
198
+ }
199
+
200
+ /* ----- Sparkline -------------------------------------------------------- */
201
+ .pa-kpi-tile__spark {
202
+ display: block;
203
+ width: 100%;
204
+ height: var(--pa-chart-trendline-height);
205
+ overflow: visible;
206
+
207
+ polyline {
208
+ fill: none;
209
+ stroke: currentColor;
210
+ stroke-width: var(--pa-chart-trendline-stroke);
211
+ stroke-linecap: round;
212
+ stroke-linejoin: round;
213
+ }
214
+ }
215
+ /* Sentiment-coloured sparkline. Class names use up/down wording but pick
216
+ colours from the sentiment-of-the-change axis, not line shape — so an
217
+ error-rate metric whose line is falling but the change is good uses
218
+ --up. See showcase docs for the rationale. Colour is set on both the
219
+ SVG and the JS-inserted .pa-kpi-spark-wrap so currentColor resolves
220
+ for the SVG content (line) and the HTML dot (inside the wrap). */
221
+ .pa-kpi-tile {
222
+ &--up-strong .pa-kpi-tile__spark,
223
+ &--up-strong .pa-kpi-spark-wrap { color: var(--pa-very-positive); }
224
+
225
+ &--up .pa-kpi-tile__spark,
226
+ &--up .pa-kpi-spark-wrap { color: var(--pa-positive); }
227
+
228
+ &--flat .pa-kpi-tile__spark,
229
+ &--flat .pa-kpi-spark-wrap { color: var(--pa-neutral); }
230
+
231
+ &--down .pa-kpi-tile__spark,
232
+ &--down .pa-kpi-spark-wrap { color: var(--pa-negative); }
233
+
234
+ &--down-strong .pa-kpi-tile__spark,
235
+ &--down-strong .pa-kpi-spark-wrap { color: var(--pa-very-negative); }
236
+ }
@@ -5,3 +5,14 @@
5
5
 
6
6
  // Core framework (includes variables and all core-components)
7
7
  @use 'core' as *;
8
+
9
+ // CSS variable defaults at :root. Themes don't go through main.scss — they
10
+ // @import core + base-css-variables and emit their own values — so this only
11
+ // affects the unthemed bundle (dist/css/main.css) and the FOUC window before
12
+ // a theme stylesheet resolves.
13
+ @use 'base-css-variables' as bcv;
14
+ :root {
15
+ @include bcv.output-base-css-variables;
16
+ @include bcv.output-pa-css-variables;
17
+ @include bcv.output-pa-alert-variables-light;
18
+ }