@umituz/web-dashboard 4.4.0 → 4.5.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": "4.4.0",
3
+ "version": "4.5.0",
4
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",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -10,14 +10,21 @@ describe('charts markup smoke', () => {
10
10
  expect(renderToString(<Sparkline data={[1]} />)).toBe('');
11
11
  });
12
12
 
13
- it('BarChart renders one column per datum with height proportional to value', () => {
13
+ it('BarChart renders one grouped column per label with one bar per series', () => {
14
14
  const html = renderToString(
15
- <BarChart data={[{ label: 'mon', value: 4 }, { label: 'tue', value: 8 }, { label: 'wed', value: 0, highlight: true }]} />,
15
+ <BarChart
16
+ labels={['mon', 'tue', 'wed']}
17
+ series={[
18
+ { key: 'a', label: 'A', values: [4, 8, 0] },
19
+ { key: 'b', label: 'B', values: [2, 4, 6], tone: '#111111' },
20
+ ]}
21
+ />,
16
22
  );
17
- expect(html.match(/udb-barchart__col/g)?.length).toBe(3);
18
- expect(html).toContain('height:50%');
23
+ expect(html.match(/udb-barchart__col(?![a-z])/g)?.length).toBe(3);
24
+ expect(html.match(/udb-barchart__bar(?![a-z_])/g)?.length).toBe(6);
19
25
  expect(html).toContain('height:100%');
20
- expect(html).toContain('#c2410c');
26
+ expect(html).toContain('height:50%');
27
+ expect(html).toContain('#111111');
21
28
  });
22
29
 
23
30
  it('Donut renders a segment circle per label with dash math and center fallback', () => {
@@ -138,51 +138,63 @@ export function HBars({ rows, formatValue = (n: number) => String(n) }: { rows:
138
138
  );
139
139
  }
140
140
 
141
- export interface BarDatum {
141
+ export interface BarSeries {
142
+ key: string;
142
143
  label: string;
143
- value: number;
144
+ values: number[];
144
145
  tone?: string;
145
- highlight?: boolean;
146
146
  }
147
147
 
148
148
  export function BarChart({
149
- data,
149
+ labels,
150
+ series,
150
151
  height = 130,
151
- tone = PALETTE[0]!,
152
- highlightTone = '#c2410c',
153
152
  formatValue = (v: number) => String(v),
154
153
  formatLabel = (l: string) => l,
155
154
  }: {
156
- data: BarDatum[];
155
+ labels: string[];
156
+ series: BarSeries[];
157
157
  height?: number;
158
- tone?: string;
159
- highlightTone?: string;
160
158
  formatValue?: (v: number) => string;
161
159
  formatLabel?: (l: string) => string;
162
160
  }) {
163
161
  const [hover, setHover] = useState<number | null>(null);
164
- const max = Math.max(1, ...data.map((d) => d.value));
162
+ const max = Math.max(1, ...series.flatMap((s) => [...s.values]));
163
+ const toneOf = (s: BarSeries, i: number) => s.tone ?? PALETTE[i % PALETTE.length]!;
165
164
  return (
166
165
  <div className="udb-barchart" style={{ height }}>
167
- {data.map((d, i) => (
166
+ {labels.map((label, i) => (
168
167
  <div
169
- key={i}
168
+ key={label}
170
169
  className="udb-barchart__col"
171
170
  onMouseEnter={() => setHover(i)}
172
171
  onMouseLeave={() => setHover(null)}
173
172
  >
174
- <div
175
- className="udb-barchart__bar"
176
- style={{
177
- height: `${Math.max(2, (d.value / max) * 100)}%`,
178
- background: d.highlight ? highlightTone : (d.tone ?? tone),
179
- opacity: hover === null || hover === i ? 1 : 0.4,
180
- }}
181
- />
173
+ <div className="udb-barchart__bars">
174
+ {series.map((s, si) => (
175
+ <div
176
+ key={s.key}
177
+ className="udb-barchart__bar"
178
+ style={{
179
+ height: `${Math.max(2, ((s.values[i] ?? 0) / max) * 100)}%`,
180
+ background: toneOf(s, si),
181
+ opacity: hover === null || hover === i ? 1 : 0.4,
182
+ }}
183
+ />
184
+ ))}
185
+ </div>
182
186
  {hover === i && (
183
187
  <div className="udb-barchart__tip">
184
- <b>{formatValue(d.value)}</b>
185
- {formatLabel(d.label)}
188
+ <div className="udb-barchart__tiptitle">{formatLabel(label)}</div>
189
+ {series.map((s, si) => (
190
+ <div key={s.key} className="udb-barchart__tiprow">
191
+ <span className="udb-barchart__tiplabel">
192
+ <span className="udb-chartlegend__dot" style={{ background: toneOf(s, si) }} />
193
+ {s.label}
194
+ </span>
195
+ <b>{formatValue(s.values[i] ?? 0)}</b>
196
+ </div>
197
+ ))}
186
198
  </div>
187
199
  )}
188
200
  </div>
@@ -5,7 +5,7 @@ export type {
5
5
  ChartCardProps,
6
6
  LegendItem,
7
7
  HBarRow,
8
- BarDatum,
8
+ BarSeries,
9
9
  DonutSegment,
10
10
  LineSeries,
11
11
  LineChartPoint,
@@ -2925,6 +2925,20 @@
2925
2925
  height: 100%;
2926
2926
  }
2927
2927
 
2928
+ .udb-barchart__bars {
2929
+ display: flex;
2930
+ align-items: flex-end;
2931
+ justify-content: center;
2932
+ gap: 2px;
2933
+ width: 100%;
2934
+ height: 100%;
2935
+ }
2936
+
2937
+ .udb-barchart__bars .udb-barchart__bar {
2938
+ flex: 1;
2939
+ width: auto;
2940
+ }
2941
+
2928
2942
  .udb-barchart__bar {
2929
2943
  width: 100%;
2930
2944
  min-height: 2px;
@@ -2948,6 +2962,24 @@
2948
2962
  color: var(--u-color-muted);
2949
2963
  }
2950
2964
 
2965
+ .udb-barchart__tiptitle {
2966
+ font-weight: var(--u-font-weight-medium);
2967
+ color: var(--u-color-text);
2968
+ }
2969
+
2970
+ .udb-barchart__tiprow {
2971
+ display: flex;
2972
+ align-items: center;
2973
+ justify-content: space-between;
2974
+ gap: 10px;
2975
+ }
2976
+
2977
+ .udb-barchart__tiplabel {
2978
+ display: inline-flex;
2979
+ align-items: center;
2980
+ gap: 5px;
2981
+ }
2982
+
2951
2983
  .udb-barchart__tip b {
2952
2984
  margin-right: 4px;
2953
2985
  font-weight: var(--u-font-weight-medium);