@umituz/web-dashboard 4.2.0 → 4.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/web-dashboard",
3
- "version": "4.2.0",
4
- "description": "Installable app skeleton for the fleet — layouts, auth, onboarding, analytics (dependency-free SVG charts), calendar, i18n injection, permissions/session/module registry; ships its own udb-* stylesheet and --u-* tokens",
3
+ "version": "4.3.0",
4
+ "description": "Installable app skeleton for the fleet \u2014 layouts, auth, onboarding, analytics (dependency-free SVG charts), calendar, i18n injection, permissions/session/module registry; ships its own udb-* stylesheet and --u-* tokens",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "types": "./src/index.ts",
@@ -1,3 +1,4 @@
1
+ import { useId } from 'react';
1
2
  import type { ReactNode } from 'react';
2
3
  import type { TipState } from './utils';
3
4
 
@@ -72,3 +73,67 @@ export function Legend({ items }: { items: LegendItem[] }) {
72
73
  </div>
73
74
  );
74
75
  }
76
+
77
+ export function Sparkline({
78
+ data,
79
+ height = 34,
80
+ width = 110,
81
+ color = 'currentColor',
82
+ }: {
83
+ data: number[];
84
+ height?: number;
85
+ width?: number;
86
+ color?: string;
87
+ }) {
88
+ const id = useId();
89
+ if (data.length < 2) return null;
90
+ const min = Math.min(...data);
91
+ const max = Math.max(...data);
92
+ const span = max - min || 1;
93
+ const pts = data.map((v, i) => [(i / (data.length - 1)) * width, height - 3 - ((v - min) / span) * (height - 8)]);
94
+ const line = pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0]!.toFixed(1)},${p[1]!.toFixed(1)}`).join(' ');
95
+ const area = `${line} L${width},${height} L0,${height} Z`;
96
+ return (
97
+ <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} aria-hidden>
98
+ <defs>
99
+ <linearGradient id={`sp${id}`} x1="0" y1="0" x2="0" y2="1">
100
+ <stop offset="0%" stopColor={color} stopOpacity="0.28" />
101
+ <stop offset="100%" stopColor={color} stopOpacity="0" />
102
+ </linearGradient>
103
+ </defs>
104
+ <path d={area} fill={`url(#sp${id})`} />
105
+ <path d={line} fill="none" stroke={color} strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round" />
106
+ <circle cx={pts.at(-1)![0]} cy={pts.at(-1)![1]} r="2.4" fill={color} />
107
+ </svg>
108
+ );
109
+ }
110
+
111
+ export interface HBarRow {
112
+ label: ReactNode;
113
+ sub?: ReactNode;
114
+ value: number;
115
+ tone?: string;
116
+ }
117
+
118
+ export function HBars({ rows, formatValue = (n: number) => String(n) }: { rows: HBarRow[]; formatValue?: (n: number) => string }) {
119
+ const max = Math.max(1, ...rows.map((r) => r.value));
120
+ return (
121
+ <div className="udb-hbars">
122
+ {rows.map((r, i) => (
123
+ <div key={i} className="udb-hbars__row">
124
+ <div className="udb-hbars__label">
125
+ <div className="udb-hbars__name">{r.label}</div>
126
+ {r.sub && <div className="udb-hbars__sub">{r.sub}</div>}
127
+ </div>
128
+ <div className="udb-hbars__track">
129
+ <div
130
+ className="udb-hbars__fill"
131
+ style={{ width: `${(r.value / max) * 100}%`, ...(r.tone ? { background: r.tone } : {}) }}
132
+ />
133
+ </div>
134
+ <span className="udb-hbars__value">{formatValue(r.value)}</span>
135
+ </div>
136
+ ))}
137
+ </div>
138
+ );
139
+ }
@@ -1,4 +1,4 @@
1
1
  export { PALETTE, useTip, niceMax, gridTicks } from './utils';
2
2
  export type { TipRow, TipState } from './utils';
3
- export { ChartTip, ChartCard, Legend } from './components';
4
- export type { ChartCardProps, LegendItem } from './components';
3
+ export { ChartTip, ChartCard, Legend, Sparkline, HBars } from './components';
4
+ export type { ChartCardProps, LegendItem, HBarRow } from './components';
@@ -2857,3 +2857,54 @@
2857
2857
  align-items: center;
2858
2858
  gap: 5px;
2859
2859
  }
2860
+
2861
+ .udb-hbars {
2862
+ display: flex;
2863
+ flex-direction: column;
2864
+ gap: 10px;
2865
+ }
2866
+
2867
+ .udb-hbars__row {
2868
+ display: grid;
2869
+ grid-template-columns: minmax(120px, 1.2fr) 2fr auto;
2870
+ align-items: center;
2871
+ gap: 12px;
2872
+ }
2873
+
2874
+ .udb-hbars__label {
2875
+ min-width: 0;
2876
+ }
2877
+
2878
+ .udb-hbars__name {
2879
+ font-weight: 600;
2880
+ font-size: var(--u-font-size-sm);
2881
+ overflow: hidden;
2882
+ text-overflow: ellipsis;
2883
+ white-space: nowrap;
2884
+ color: var(--u-color-text);
2885
+ }
2886
+
2887
+ .udb-hbars__sub {
2888
+ font-size: var(--u-font-size-xs);
2889
+ color: var(--u-color-muted);
2890
+ }
2891
+
2892
+ .udb-hbars__track {
2893
+ height: 8px;
2894
+ background: var(--u-color-border);
2895
+ border-radius: 999px;
2896
+ overflow: hidden;
2897
+ }
2898
+
2899
+ .udb-hbars__fill {
2900
+ height: 100%;
2901
+ background: var(--u-color-primary);
2902
+ border-radius: 999px;
2903
+ }
2904
+
2905
+ .udb-hbars__value {
2906
+ font-size: var(--u-font-size-sm);
2907
+ color: var(--u-color-text);
2908
+ text-align: right;
2909
+ font-variant-numeric: tabular-nums;
2910
+ }