@umituz/web-dashboard 6.2.0 → 6.3.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-dashboard",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Installable app skeleton for the fleet — layouts, analytics (dependency-free SVG charts), calendar, onboarding flow, i18n via @umituz/web-localization, permissions/module registry; udb-* stylesheet on design-system tokens",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -86,6 +86,29 @@ describe('charts markup smoke', () => {
|
|
|
86
86
|
expect(html).toContain('height:100%');
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
it('BarChart renders a dashed goal line that counts toward max and clamps at 100%', () => {
|
|
90
|
+
const html = renderToString(
|
|
91
|
+
<BarChart
|
|
92
|
+
labels={['a', 'b']}
|
|
93
|
+
series={[{ label: 'A', values: [2, 4] }]}
|
|
94
|
+
goalLine={{ value: 4, label: 'goal 4', tone: '#111111' }}
|
|
95
|
+
/>,
|
|
96
|
+
);
|
|
97
|
+
expect(html).toContain('udb-barchart__goal');
|
|
98
|
+
expect(html).toContain('bottom:100%');
|
|
99
|
+
expect(html).toContain('border-top-color:#111111');
|
|
100
|
+
expect(html).toContain('>goal 4<');
|
|
101
|
+
|
|
102
|
+
const above = renderToString(
|
|
103
|
+
<BarChart labels={['a']} series={[{ label: 'A', values: [2] }]} goalLine={{ value: 99 }} />,
|
|
104
|
+
);
|
|
105
|
+
expect(above).toContain('bottom:100%');
|
|
106
|
+
expect(above).not.toContain('udb-barchart__goallabel');
|
|
107
|
+
|
|
108
|
+
const without = renderToString(<BarChart labels={['a']} series={[{ label: 'A', values: [2] }]} />);
|
|
109
|
+
expect(without).not.toContain('udb-barchart__goal');
|
|
110
|
+
});
|
|
111
|
+
|
|
89
112
|
it('fleet aliases: Legend renders hint suffix and Donut accepts color', () => {
|
|
90
113
|
const html = renderToString(
|
|
91
114
|
<>
|
|
@@ -149,25 +149,58 @@ export interface BarSeries {
|
|
|
149
149
|
color?: string;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
export interface BarChartGoalLine {
|
|
153
|
+
value: number;
|
|
154
|
+
label?: string;
|
|
155
|
+
tone?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
152
158
|
export function BarChart({
|
|
153
159
|
labels,
|
|
154
160
|
series,
|
|
155
161
|
height = 130,
|
|
156
162
|
formatValue = (v: number) => String(v),
|
|
157
163
|
formatLabel = (l: string) => l,
|
|
164
|
+
goalLine,
|
|
158
165
|
}: {
|
|
159
166
|
labels: readonly string[];
|
|
160
167
|
series: BarSeries[];
|
|
161
168
|
height?: number;
|
|
162
169
|
formatValue?: (v: number) => string;
|
|
163
170
|
formatLabel?: (l: string) => string;
|
|
171
|
+
goalLine?: BarChartGoalLine;
|
|
164
172
|
}) {
|
|
165
173
|
const [hover, setHover] = useState<number | null>(null);
|
|
166
174
|
const vals = (s: BarSeries) => s.values ?? s.data ?? [];
|
|
167
|
-
const max = Math.max(1, ...series.flatMap((s) => vals(s)));
|
|
175
|
+
const max = Math.max(1, ...series.flatMap((s) => vals(s)), goalLine?.value ?? 0);
|
|
168
176
|
const toneOf = (s: BarSeries, i: number) => s.tone ?? s.color ?? PALETTE[i % PALETTE.length]!;
|
|
177
|
+
const goalPct = goalLine !== undefined ? Math.min(100, (goalLine.value / max) * 100) : null;
|
|
169
178
|
return (
|
|
170
179
|
<div className="udb-barchart" style={{ height }}>
|
|
180
|
+
{goalLine !== undefined && goalPct !== null && (
|
|
181
|
+
<div
|
|
182
|
+
className="udb-barchart__goal"
|
|
183
|
+
style={{
|
|
184
|
+
bottom: `${goalPct}%`,
|
|
185
|
+
...(goalLine.tone !== undefined ? { borderTopColor: goalLine.tone } : {}),
|
|
186
|
+
}}
|
|
187
|
+
title={goalLine.label ?? formatValue(goalLine.value)}
|
|
188
|
+
aria-hidden="true"
|
|
189
|
+
>
|
|
190
|
+
{goalLine.label !== undefined && (
|
|
191
|
+
<span
|
|
192
|
+
className="udb-barchart__goallabel"
|
|
193
|
+
style={
|
|
194
|
+
goalLine.tone !== undefined
|
|
195
|
+
? { color: goalLine.tone, borderColor: goalLine.tone }
|
|
196
|
+
: undefined
|
|
197
|
+
}
|
|
198
|
+
>
|
|
199
|
+
{goalLine.label}
|
|
200
|
+
</span>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
171
204
|
{labels.map((label, i) => (
|
|
172
205
|
<div
|
|
173
206
|
key={label}
|
package/src/styles/dashboard.css
CHANGED
|
@@ -704,7 +704,6 @@
|
|
|
704
704
|
.udb-spin {
|
|
705
705
|
animation: udb-spin 1s linear infinite;
|
|
706
706
|
}
|
|
707
|
-
}
|
|
708
707
|
.udb-settings {
|
|
709
708
|
display: flex;
|
|
710
709
|
width: 100%;
|
|
@@ -1419,11 +1418,32 @@
|
|
|
1419
1418
|
font-variant-numeric: tabular-nums;
|
|
1420
1419
|
}
|
|
1421
1420
|
.udb-barchart {
|
|
1421
|
+
position: relative;
|
|
1422
1422
|
display: flex;
|
|
1423
1423
|
align-items: flex-end;
|
|
1424
1424
|
gap: 3px;
|
|
1425
1425
|
width: 100%;
|
|
1426
1426
|
}
|
|
1427
|
+
.udb-barchart__goal {
|
|
1428
|
+
position: absolute;
|
|
1429
|
+
left: 0;
|
|
1430
|
+
right: 0;
|
|
1431
|
+
z-index: 5;
|
|
1432
|
+
pointer-events: none;
|
|
1433
|
+
border-top: 1.5px dashed var(--udb-chart-goal, #d97706);
|
|
1434
|
+
}
|
|
1435
|
+
.udb-barchart__goallabel {
|
|
1436
|
+
position: absolute;
|
|
1437
|
+
right: 0;
|
|
1438
|
+
bottom: 2px;
|
|
1439
|
+
padding: 0 4px;
|
|
1440
|
+
border: 1px solid var(--udb-chart-goal, #d97706);
|
|
1441
|
+
border-radius: var(--udb-radius-pill);
|
|
1442
|
+
background: var(--color-card);
|
|
1443
|
+
font-size: var(--udb-font-size-xs);
|
|
1444
|
+
line-height: 16px;
|
|
1445
|
+
color: var(--udb-chart-goal, #d97706);
|
|
1446
|
+
}
|
|
1427
1447
|
.udb-barchart__col {
|
|
1428
1448
|
position: relative;
|
|
1429
1449
|
display: flex;
|