@watt-ui/react 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/NOTICE +43 -0
- package/README.md +48 -2
- package/dist/chart/data.d.ts +13 -0
- package/dist/chart/data.d.ts.map +1 -0
- package/dist/chart/data.js +92 -0
- package/dist/chart/data.js.map +1 -0
- package/dist/chart/engine.d.ts +2 -0
- package/dist/chart/engine.d.ts.map +1 -0
- package/dist/chart/engine.js +16 -0
- package/dist/chart/engine.js.map +1 -0
- package/dist/chart/options.d.ts +18 -0
- package/dist/chart/options.d.ts.map +1 -0
- package/dist/chart/options.js +136 -0
- package/dist/chart/options.js.map +1 -0
- package/dist/components/alarm-feed.d.ts +18 -0
- package/dist/components/alarm-feed.d.ts.map +1 -0
- package/dist/components/alarm-feed.js +45 -0
- package/dist/components/alarm-feed.js.map +1 -0
- package/dist/components/asset-tree.d.ts +33 -0
- package/dist/components/asset-tree.d.ts.map +1 -0
- package/dist/components/asset-tree.js +149 -0
- package/dist/components/asset-tree.js.map +1 -0
- package/dist/components/battery-soc.d.ts +11 -0
- package/dist/components/battery-soc.d.ts.map +1 -0
- package/dist/components/battery-soc.js +34 -0
- package/dist/components/battery-soc.js.map +1 -0
- package/dist/components/data-freshness.d.ts +26 -0
- package/dist/components/data-freshness.d.ts.map +1 -0
- package/dist/components/data-freshness.js +45 -0
- package/dist/components/data-freshness.js.map +1 -0
- package/dist/components/energy-flow.d.ts +39 -0
- package/dist/components/energy-flow.d.ts.map +1 -0
- package/dist/components/energy-flow.js +176 -0
- package/dist/components/energy-flow.js.map +1 -0
- package/dist/components/flow-geometry.d.ts +24 -0
- package/dist/components/flow-geometry.d.ts.map +1 -0
- package/dist/components/flow-geometry.js +57 -0
- package/dist/components/flow-geometry.js.map +1 -0
- package/dist/components/power-metric.d.ts +14 -0
- package/dist/components/power-metric.d.ts.map +1 -0
- package/dist/components/power-metric.js +25 -0
- package/dist/components/power-metric.js.map +1 -0
- package/dist/components/status-indicator.d.ts +10 -0
- package/dist/components/status-indicator.d.ts.map +1 -0
- package/dist/components/status-indicator.js +15 -0
- package/dist/components/status-indicator.js.map +1 -0
- package/dist/components/telemetry-chart.d.ts +50 -0
- package/dist/components/telemetry-chart.d.ts.map +1 -0
- package/dist/components/telemetry-chart.js +256 -0
- package/dist/components/telemetry-chart.js.map +1 -0
- package/dist/domain.d.ts +6 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +43 -0
- package/dist/domain.js.map +1 -0
- package/dist/icons/index.d.ts +7 -0
- package/dist/icons/index.d.ts.map +1 -0
- package/dist/icons/index.js +38 -0
- package/dist/icons/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +6 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +45 -0
- package/dist/internal.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +32 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.css +130 -0
- package/dist/theme/shadcn.css +14 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +118 -4
- package/src/chart/data.ts +103 -0
- package/src/chart/engine.ts +19 -0
- package/src/chart/options.ts +178 -0
- package/src/components/alarm-feed.tsx +126 -0
- package/src/components/asset-tree.tsx +258 -0
- package/src/components/battery-soc.tsx +84 -0
- package/src/components/data-freshness.tsx +109 -0
- package/src/components/energy-flow.tsx +364 -0
- package/src/components/flow-geometry.ts +82 -0
- package/src/components/power-metric.tsx +71 -0
- package/src/components/status-indicator.tsx +31 -0
- package/src/components/telemetry-chart.tsx +476 -0
- package/src/domain.ts +64 -0
- package/src/icons/index.tsx +49 -0
- package/src/index.ts +37 -0
- package/src/internal.tsx +57 -0
- package/src/server.ts +47 -0
- package/src/styles.css +130 -0
- package/src/theme/shadcn.css +14 -0
- package/src/types.ts +64 -0
package/dist/internal.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useSyncExternalStore } from "react";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import { clsx } from "clsx";
|
|
6
|
+
import { twMerge } from "tailwind-merge";
|
|
7
|
+
export function cx(...values) {
|
|
8
|
+
return twMerge(clsx(values));
|
|
9
|
+
}
|
|
10
|
+
export function part(slot, defaults = "", tag = "div") {
|
|
11
|
+
const Part = forwardRef(({ asChild, className, ...props }, ref) => {
|
|
12
|
+
const Component = asChild ? Slot : tag;
|
|
13
|
+
return (_jsx(Component, { ...props, ref: ref, "data-slot": slot, className: cx(defaults, className) }));
|
|
14
|
+
});
|
|
15
|
+
Part.displayName = slot;
|
|
16
|
+
return Part;
|
|
17
|
+
}
|
|
18
|
+
let instant = null;
|
|
19
|
+
let timer;
|
|
20
|
+
const listeners = new Set();
|
|
21
|
+
function subscribe(listener) {
|
|
22
|
+
listeners.add(listener);
|
|
23
|
+
if (!timer) {
|
|
24
|
+
instant = Date.now();
|
|
25
|
+
timer = setInterval(() => {
|
|
26
|
+
instant = Date.now();
|
|
27
|
+
listeners.forEach((fn) => fn());
|
|
28
|
+
}, 1000);
|
|
29
|
+
}
|
|
30
|
+
return () => {
|
|
31
|
+
listeners.delete(listener);
|
|
32
|
+
if (!listeners.size) {
|
|
33
|
+
clearInterval(timer);
|
|
34
|
+
timer = undefined;
|
|
35
|
+
instant = null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const snapshot = () => instant;
|
|
40
|
+
const serverSnapshot = () => null;
|
|
41
|
+
const noopSubscribe = () => () => { };
|
|
42
|
+
export function useNow(enabled) {
|
|
43
|
+
return useSyncExternalStore(enabled ? subscribe : noopSubscribe, snapshot, serverSnapshot);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAmB,MAAM,MAAM,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,MAAM,UAAU,EAAE,CAAC,GAAG,MAAoB;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/B,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,QAAQ,GAAG,EAAE,EAAE,MAAsB,KAAK;IAC3E,MAAM,IAAI,GAAG,UAAU,CACrB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE;QACxC,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QACvC,OAAO,CACL,KAAC,SAAS,OACJ,KAAK,EACT,GAAG,EAAE,GAAY,eACN,IAAI,EACf,SAAS,EAAE,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,GAClC,CACH,CAAC;IACJ,CAAC,CACF,CAAC;IACF,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AACD,IAAI,OAAO,GAAkB,IAAI,CAAC;AAClC,IAAI,KAAiD,CAAC;AACtD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;AACxC,SAAS,SAAS,CAAC,QAAoB;IACrC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YACvB,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACrB,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,CAAC,EAAE,IAAI,CAAC,CAAC;IACX,CAAC;IACD,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;YACpB,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,KAAK,GAAG,SAAS,CAAC;YAClB,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AACD,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC;AAC/B,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;AAClC,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;AACrC,MAAM,UAAU,MAAM,CAAC,OAAgB;IACrC,OAAO,oBAAoB,CACzB,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EACnC,QAAQ,EACR,cAAc,CACf,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ChartConfig } from "./chart/options.js";
|
|
2
|
+
import type { TelemetrySeries } from "./types.js";
|
|
3
|
+
export interface ServerChartProps extends Omit<ChartConfig, "series"> {
|
|
4
|
+
series: readonly TelemetrySeries[];
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
/** Render on the server and pass this trusted output to TelemetryChart.Canvas previewSvg. */
|
|
9
|
+
export declare function renderTelemetrySVG({ width, height, series, ...config }: ServerChartProps): string;
|
|
10
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;IACnE,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,MAAM,EACN,GAAG,MAAM,EACV,EAAE,gBAAgB,GAAG,MAAM,CAwB3B"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { init, use } from "echarts/core";
|
|
2
|
+
import { LineChart } from "echarts/charts";
|
|
3
|
+
import { GridComponent, TooltipComponent, DataZoomComponent, } from "echarts/components";
|
|
4
|
+
import { SVGRenderer } from "echarts/renderers";
|
|
5
|
+
import { prepareSeries } from "./chart/data.js";
|
|
6
|
+
import { chartOption } from "./chart/options.js";
|
|
7
|
+
/** Render on the server and pass this trusted output to TelemetryChart.Canvas previewSvg. */
|
|
8
|
+
export function renderTelemetrySVG({ width, height, series, ...config }) {
|
|
9
|
+
if (![width, height].every((v) => Number.isFinite(v) && v > 0))
|
|
10
|
+
throw new Error("SSR charts require positive width and height");
|
|
11
|
+
use([
|
|
12
|
+
LineChart,
|
|
13
|
+
GridComponent,
|
|
14
|
+
TooltipComponent,
|
|
15
|
+
DataZoomComponent,
|
|
16
|
+
SVGRenderer,
|
|
17
|
+
]);
|
|
18
|
+
const chart = init(null, undefined, {
|
|
19
|
+
renderer: "svg",
|
|
20
|
+
ssr: true,
|
|
21
|
+
width,
|
|
22
|
+
height,
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
chart.setOption(chartOption({ ...config, series: prepareSeries(series) }, width));
|
|
26
|
+
return chart.renderToSVGString();
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
chart.dispose();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AAOnE,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,MAAM,EACN,GAAG,MAAM,EACQ;IACjB,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,GAAG,CAAC;QACF,SAAS;QACT,aAAa;QACb,gBAAgB;QAChB,iBAAiB;QACjB,WAAW;KACZ,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE;QAClC,QAAQ,EAAE,KAAK;QACf,GAAG,EAAE,IAAI;QACT,KAAK;QACL,MAAM;KACP,CAAC,CAAC;IACH,IAAI,CAAC;QACH,KAAK,CAAC,SAAS,CACb,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CACjE,CAAC;QACF,OAAO,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACnC,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
@source "./**/*.js";
|
|
2
|
+
@theme inline {
|
|
3
|
+
--color-watt-positive: var(--watt-positive);
|
|
4
|
+
--color-watt-warning: var(--watt-warning);
|
|
5
|
+
--color-watt-critical: var(--watt-critical);
|
|
6
|
+
--color-watt-accent: var(--watt-accent);
|
|
7
|
+
--color-watt-surface: var(--watt-surface);
|
|
8
|
+
--color-watt-foreground: var(--watt-foreground);
|
|
9
|
+
--color-watt-muted: var(--watt-muted);
|
|
10
|
+
--color-watt-border: var(--watt-border);
|
|
11
|
+
--color-watt-subtle: var(--watt-subtle);
|
|
12
|
+
}
|
|
13
|
+
:root,
|
|
14
|
+
.watt-theme {
|
|
15
|
+
--watt-surface: #ffffff;
|
|
16
|
+
--watt-foreground: #20232c;
|
|
17
|
+
--watt-muted: #626b7b;
|
|
18
|
+
--watt-border: #e3e6ec;
|
|
19
|
+
--watt-subtle: #f1f3f7;
|
|
20
|
+
--watt-accent: #355dff;
|
|
21
|
+
--watt-positive: #16806b;
|
|
22
|
+
--watt-warning: #996009;
|
|
23
|
+
--watt-critical: #c43b40;
|
|
24
|
+
--watt-radius: 12px;
|
|
25
|
+
}
|
|
26
|
+
.dark,
|
|
27
|
+
[data-theme="dark"] {
|
|
28
|
+
--watt-surface: #17191f;
|
|
29
|
+
--watt-foreground: #eef0f5;
|
|
30
|
+
--watt-muted: #a4adbe;
|
|
31
|
+
--watt-border: #333743;
|
|
32
|
+
--watt-subtle: #252932;
|
|
33
|
+
--watt-accent: #8ba3ff;
|
|
34
|
+
--watt-positive: #61d4b7;
|
|
35
|
+
--watt-warning: #f1bd65;
|
|
36
|
+
--watt-critical: #ff898d;
|
|
37
|
+
}
|
|
38
|
+
.watt-root {
|
|
39
|
+
color: var(--watt-foreground);
|
|
40
|
+
font-variant-numeric: tabular-nums;
|
|
41
|
+
}
|
|
42
|
+
.watt-status {
|
|
43
|
+
color: var(--watt-muted);
|
|
44
|
+
}
|
|
45
|
+
.watt-status[data-status="normal"] {
|
|
46
|
+
color: var(--watt-positive);
|
|
47
|
+
}
|
|
48
|
+
.watt-status[data-status="warning"] {
|
|
49
|
+
color: var(--watt-warning);
|
|
50
|
+
}
|
|
51
|
+
.watt-status[data-status="fault"] {
|
|
52
|
+
color: var(--watt-critical);
|
|
53
|
+
}
|
|
54
|
+
.watt-status-dot {
|
|
55
|
+
display: inline-block;
|
|
56
|
+
width: 0.5rem;
|
|
57
|
+
height: 0.5rem;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
background: currentColor;
|
|
60
|
+
flex-shrink: 0;
|
|
61
|
+
}
|
|
62
|
+
[data-status="standby"] .watt-status-dot {
|
|
63
|
+
background: transparent;
|
|
64
|
+
border: 1.5px solid currentColor;
|
|
65
|
+
}
|
|
66
|
+
[data-status="stopped"] .watt-status-dot {
|
|
67
|
+
border-radius: 1px;
|
|
68
|
+
}
|
|
69
|
+
[data-status="warning"] .watt-status-dot {
|
|
70
|
+
border-radius: 1px;
|
|
71
|
+
transform: rotate(45deg);
|
|
72
|
+
}
|
|
73
|
+
[data-status="fault"] .watt-status-dot {
|
|
74
|
+
border-radius: 0;
|
|
75
|
+
clip-path: polygon(
|
|
76
|
+
25% 0,
|
|
77
|
+
75% 0,
|
|
78
|
+
100% 25%,
|
|
79
|
+
100% 75%,
|
|
80
|
+
75% 100%,
|
|
81
|
+
25% 100%,
|
|
82
|
+
0 75%,
|
|
83
|
+
0 25%
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
[data-status="offline"] .watt-status-dot {
|
|
87
|
+
background: transparent;
|
|
88
|
+
border: 1px dashed currentColor;
|
|
89
|
+
}
|
|
90
|
+
.watt-flow-dash {
|
|
91
|
+
stroke-dasharray: 7 6;
|
|
92
|
+
animation: watt-flow-dash 3s linear infinite;
|
|
93
|
+
}
|
|
94
|
+
.watt-flow-pulse {
|
|
95
|
+
animation: watt-flow-pulse 3s ease-in-out infinite;
|
|
96
|
+
}
|
|
97
|
+
.watt-flow-particle {
|
|
98
|
+
animation: watt-flow-particle 3s linear infinite;
|
|
99
|
+
}
|
|
100
|
+
.watt-chart-preview svg {
|
|
101
|
+
width: 100%;
|
|
102
|
+
height: 100%;
|
|
103
|
+
}
|
|
104
|
+
@keyframes watt-flow-dash {
|
|
105
|
+
to {
|
|
106
|
+
stroke-dashoffset: -26;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
@keyframes watt-flow-pulse {
|
|
110
|
+
50% {
|
|
111
|
+
opacity: 0.35;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
@keyframes watt-flow-particle {
|
|
115
|
+
from {
|
|
116
|
+
offset-distance: 0%;
|
|
117
|
+
}
|
|
118
|
+
to {
|
|
119
|
+
offset-distance: 100%;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
@media (prefers-reduced-motion: reduce) {
|
|
123
|
+
.watt-flow-dash,
|
|
124
|
+
.watt-flow-pulse {
|
|
125
|
+
animation: none !important;
|
|
126
|
+
}
|
|
127
|
+
.watt-flow-particle {
|
|
128
|
+
display: none;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* Import after @watt-ui/react/styles.css inside a shadcn project. */
|
|
2
|
+
:root,
|
|
3
|
+
.dark,
|
|
4
|
+
[data-theme],
|
|
5
|
+
.watt-theme {
|
|
6
|
+
--watt-surface: var(--background);
|
|
7
|
+
--watt-foreground: var(--foreground);
|
|
8
|
+
--watt-muted: var(--muted-foreground);
|
|
9
|
+
--watt-border: var(--border);
|
|
10
|
+
--watt-subtle: var(--muted);
|
|
11
|
+
--watt-accent: var(--primary);
|
|
12
|
+
--watt-critical: var(--destructive);
|
|
13
|
+
--watt-radius: var(--radius);
|
|
14
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
export type PowerUnit = "W" | "kW" | "MW";
|
|
3
|
+
export type EnergyUnit = "Wh" | "kWh" | "MWh";
|
|
4
|
+
export type MeasurementUnit = PowerUnit | EnergyUnit | "V" | "A" | "Hz" | "°C" | "%" | "W/m²";
|
|
5
|
+
export type Timestamp = string | number;
|
|
6
|
+
export type DataQuality = "good" | "uncertain" | "stale" | "missing" | "bad" | "offline";
|
|
7
|
+
export type EquipmentStatus = "normal" | "standby" | "stopped" | "warning" | "fault" | "offline";
|
|
8
|
+
export type Density = "compact" | "comfortable";
|
|
9
|
+
export interface PartProps extends HTMLAttributes<HTMLElement> {
|
|
10
|
+
asChild?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface RootProps extends PartProps {
|
|
13
|
+
density?: Density;
|
|
14
|
+
}
|
|
15
|
+
export interface Measurement<U extends MeasurementUnit = MeasurementUnit> {
|
|
16
|
+
value: number | null;
|
|
17
|
+
unit: U;
|
|
18
|
+
timestamp: Timestamp;
|
|
19
|
+
quality?: DataQuality;
|
|
20
|
+
}
|
|
21
|
+
export interface TimeSeriesPoint {
|
|
22
|
+
timestamp: Timestamp;
|
|
23
|
+
value: number | null;
|
|
24
|
+
quality?: DataQuality;
|
|
25
|
+
}
|
|
26
|
+
export interface TelemetrySeries {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
unit: string;
|
|
30
|
+
points: readonly TimeSeriesPoint[];
|
|
31
|
+
color?: string;
|
|
32
|
+
axis?: 0 | 1;
|
|
33
|
+
area?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export type TimeRange = readonly [number, number];
|
|
36
|
+
export interface AlarmEvent {
|
|
37
|
+
id: string;
|
|
38
|
+
severity: "critical" | "warning" | "info";
|
|
39
|
+
status: "active" | "acknowledged" | "resolved";
|
|
40
|
+
timestamp: Timestamp;
|
|
41
|
+
message: string;
|
|
42
|
+
asset?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface EnergyFlowNode {
|
|
46
|
+
id: string;
|
|
47
|
+
}
|
|
48
|
+
export interface EnergyFlowEdge {
|
|
49
|
+
source: string;
|
|
50
|
+
target: string;
|
|
51
|
+
sourcePort?: string;
|
|
52
|
+
targetPort?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface PlantAsset {
|
|
55
|
+
id: string;
|
|
56
|
+
label: string;
|
|
57
|
+
children?: readonly PlantAsset[];
|
|
58
|
+
status?: EquipmentStatus;
|
|
59
|
+
measurement?: Measurement;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAE5C,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1C,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC;AAC9C,MAAM,MAAM,eAAe,GACzB,SAAS,GAAG,UAAU,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,MAAM,CAAC;AAClE,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AACxC,MAAM,MAAM,WAAW,GACrB,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AACjE,MAAM,MAAM,eAAe,GACzB,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AACrE,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AAChD,MAAM,WAAW,SAAU,SAAQ,cAAc,CAAC,WAAW,CAAC;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AACD,MAAM,WAAW,SAAU,SAAQ,SAAS;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AACD,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe;IACtE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AACD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AACD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AACD,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAClD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IAC1C,MAAM,EAAE,QAAQ,GAAG,cAAc,GAAG,UAAU,CAAC;IAC/C,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AACD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;CACZ;AACD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AACD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACjC,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,120 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@watt-ui/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React components for energy and power dashboards",
|
|
5
|
+
"homepage": "https://watt-ui.pages.dev",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"NOTICE",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"sideEffects": [
|
|
16
|
+
"**/*.css"
|
|
17
|
+
],
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./icons": {
|
|
26
|
+
"types": "./dist/icons/index.d.ts",
|
|
27
|
+
"import": "./dist/icons/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./types": {
|
|
30
|
+
"types": "./dist/types.d.ts",
|
|
31
|
+
"import": "./dist/types.js"
|
|
32
|
+
},
|
|
33
|
+
"./styles.css": "./dist/styles.css",
|
|
34
|
+
"./theme/shadcn.css": "./dist/theme/shadcn.css",
|
|
35
|
+
"./server": {
|
|
36
|
+
"types": "./dist/server.d.ts",
|
|
37
|
+
"import": "./dist/server.js"
|
|
38
|
+
},
|
|
39
|
+
"./power-metric": {
|
|
40
|
+
"types": "./dist/components/power-metric.d.ts",
|
|
41
|
+
"import": "./dist/components/power-metric.js"
|
|
42
|
+
},
|
|
43
|
+
"./status-indicator": {
|
|
44
|
+
"types": "./dist/components/status-indicator.d.ts",
|
|
45
|
+
"import": "./dist/components/status-indicator.js"
|
|
46
|
+
},
|
|
47
|
+
"./data-freshness": {
|
|
48
|
+
"types": "./dist/components/data-freshness.d.ts",
|
|
49
|
+
"import": "./dist/components/data-freshness.js"
|
|
50
|
+
},
|
|
51
|
+
"./battery-soc": {
|
|
52
|
+
"types": "./dist/components/battery-soc.d.ts",
|
|
53
|
+
"import": "./dist/components/battery-soc.js"
|
|
54
|
+
},
|
|
55
|
+
"./telemetry-chart": {
|
|
56
|
+
"types": "./dist/components/telemetry-chart.d.ts",
|
|
57
|
+
"import": "./dist/components/telemetry-chart.js"
|
|
58
|
+
},
|
|
59
|
+
"./energy-flow": {
|
|
60
|
+
"types": "./dist/components/energy-flow.d.ts",
|
|
61
|
+
"import": "./dist/components/energy-flow.js"
|
|
62
|
+
},
|
|
63
|
+
"./alarm-feed": {
|
|
64
|
+
"types": "./dist/components/alarm-feed.d.ts",
|
|
65
|
+
"import": "./dist/components/alarm-feed.js"
|
|
66
|
+
},
|
|
67
|
+
"./asset-tree": {
|
|
68
|
+
"types": "./dist/components/asset-tree.d.ts",
|
|
69
|
+
"import": "./dist/components/asset-tree.js"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"dev": "vite --host 127.0.0.1",
|
|
74
|
+
"build": "node scripts/build.mjs",
|
|
75
|
+
"build:demo": "vite build",
|
|
76
|
+
"typecheck": "tsc --noEmit",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"prepack": "npm run build",
|
|
79
|
+
"test:browser": "playwright test",
|
|
80
|
+
"check:package": "node scripts/check-package.mjs",
|
|
81
|
+
"benchmark": "node scripts/benchmark.mjs"
|
|
82
|
+
},
|
|
83
|
+
"peerDependencies": {
|
|
84
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
85
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
86
|
+
"tailwindcss": "^4.0.0"
|
|
87
|
+
},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@axe-core/playwright": "^4.14.0",
|
|
90
|
+
"@playwright/test": "^1.64.1",
|
|
91
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
92
|
+
"@testing-library/react": "^16.3.0",
|
|
93
|
+
"@testing-library/user-event": "^14.6.1",
|
|
94
|
+
"@types/node": "^24.0.0",
|
|
95
|
+
"@types/react": "^19.0.0",
|
|
96
|
+
"@types/react-dom": "^19.0.0",
|
|
97
|
+
"esbuild": "^0.28.2",
|
|
98
|
+
"jsdom": "^27.0.0",
|
|
99
|
+
"react": "^19.3.1",
|
|
100
|
+
"react-dom": "^19.3.1",
|
|
101
|
+
"tailwindcss": "^4.3.3",
|
|
102
|
+
"typescript": "^5.9.3",
|
|
103
|
+
"vite": "^8.3.5",
|
|
104
|
+
"vitest": "^5.0.5"
|
|
105
|
+
},
|
|
106
|
+
"publishConfig": {
|
|
107
|
+
"access": "public"
|
|
108
|
+
},
|
|
109
|
+
"dependencies": {
|
|
110
|
+
"@headless-tree/core": "^1.8.0",
|
|
111
|
+
"@headless-tree/react": "^1.8.0",
|
|
112
|
+
"@radix-ui/react-compose-refs": "^1.1.5",
|
|
113
|
+
"@radix-ui/react-slot": "^1.5.0",
|
|
114
|
+
"@tanstack/react-virtual": "^3.14.22",
|
|
115
|
+
"clsx": "^2.1.1",
|
|
116
|
+
"echarts": "^6.1.0",
|
|
117
|
+
"lucide-react": "^1.61.0",
|
|
118
|
+
"tailwind-merge": "^3.7.0"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { parseTimestamp } from "../domain.js";
|
|
2
|
+
import type { TelemetrySeries, TimeRange, TimeSeriesPoint } from "../types.js";
|
|
3
|
+
export type ChartPoint = [number, number | null];
|
|
4
|
+
export interface PreparedSeries extends Omit<TelemetrySeries, "points"> {
|
|
5
|
+
points: ChartPoint[];
|
|
6
|
+
sourcePoints: readonly TimeSeriesPoint[];
|
|
7
|
+
}
|
|
8
|
+
export function prepareSeries(
|
|
9
|
+
series: readonly TelemetrySeries[],
|
|
10
|
+
): PreparedSeries[] {
|
|
11
|
+
const ids = new Set<string>();
|
|
12
|
+
const units = new Map<number, string>();
|
|
13
|
+
return series.map((s) => {
|
|
14
|
+
if (!s.id || ids.has(s.id))
|
|
15
|
+
throw new Error("Chart series IDs must be nonempty and unique");
|
|
16
|
+
ids.add(s.id);
|
|
17
|
+
const axis = s.axis ?? 0;
|
|
18
|
+
if (axis !== 0 && axis !== 1)
|
|
19
|
+
throw new Error("Charts support at most two Y axes");
|
|
20
|
+
if (units.has(axis) && units.get(axis) !== s.unit)
|
|
21
|
+
throw new Error("Series on one Y axis must use the same unit");
|
|
22
|
+
units.set(axis, s.unit);
|
|
23
|
+
let last = -Infinity;
|
|
24
|
+
const points: ChartPoint[] = s.points.map((p) => {
|
|
25
|
+
const time = parseTimestamp(p.timestamp);
|
|
26
|
+
if (time === null || time < last)
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Chart timestamps must be valid ISO instants or epoch milliseconds, sorted ascending",
|
|
29
|
+
);
|
|
30
|
+
last = time;
|
|
31
|
+
return [
|
|
32
|
+
time,
|
|
33
|
+
p.value !== null && Number.isFinite(p.value) ? p.value : null,
|
|
34
|
+
];
|
|
35
|
+
});
|
|
36
|
+
return { ...s, points, sourcePoints: s.points };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Display-only min/max sampling: every explicit null remains a line break. */
|
|
40
|
+
export function samplePoints(
|
|
41
|
+
points: ChartPoint[],
|
|
42
|
+
limit: number,
|
|
43
|
+
range?: TimeRange,
|
|
44
|
+
): ChartPoint[] {
|
|
45
|
+
let start = range ? lowerBound(points, range[0]) : 0;
|
|
46
|
+
let end = range ? lowerBound(points, range[1]) + 1 : points.length;
|
|
47
|
+
start = Math.max(0, start - (range ? 1 : 0));
|
|
48
|
+
end = Math.min(points.length, end);
|
|
49
|
+
if (end - start <= limit) return points.slice(start, end);
|
|
50
|
+
const result: ChartPoint[] = [];
|
|
51
|
+
const bucket = Math.max(1, Math.ceil((end - start) / Math.max(1, limit / 2)));
|
|
52
|
+
result.push(points[start]!);
|
|
53
|
+
for (let i = start; i < end;) {
|
|
54
|
+
if (points[i]![1] === null) {
|
|
55
|
+
result.push(points[i]!);
|
|
56
|
+
i++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const stop = Math.min(end, i + bucket);
|
|
60
|
+
let min = i,
|
|
61
|
+
max = i,
|
|
62
|
+
j = i;
|
|
63
|
+
while (j < stop && points[j]![1] !== null) {
|
|
64
|
+
if (points[j]![1]! < points[min]![1]!) min = j;
|
|
65
|
+
if (points[j]![1]! > points[max]![1]!) max = j;
|
|
66
|
+
j++;
|
|
67
|
+
}
|
|
68
|
+
if (min < max) result.push(points[min]!, points[max]!);
|
|
69
|
+
else if (max < min) result.push(points[max]!, points[min]!);
|
|
70
|
+
else result.push(points[min]!);
|
|
71
|
+
i = j;
|
|
72
|
+
}
|
|
73
|
+
if (end > start) result.push(points[end - 1]!);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
export function lowerBound(points: ChartPoint[], time: number): number {
|
|
77
|
+
let lo = 0,
|
|
78
|
+
hi = points.length;
|
|
79
|
+
while (lo < hi) {
|
|
80
|
+
const mid = (lo + hi) >>> 1;
|
|
81
|
+
if (points[mid]![0] < time) lo = mid + 1;
|
|
82
|
+
else hi = mid;
|
|
83
|
+
}
|
|
84
|
+
return lo;
|
|
85
|
+
}
|
|
86
|
+
export function nearestIndex(points: ChartPoint[], time: number): number {
|
|
87
|
+
const index = lowerBound(points, time);
|
|
88
|
+
const right = points[index],
|
|
89
|
+
left = points[index - 1];
|
|
90
|
+
return !right
|
|
91
|
+
? points.length - 1
|
|
92
|
+
: !left
|
|
93
|
+
? index
|
|
94
|
+
: time - left[0] <= right[0] - time
|
|
95
|
+
? index - 1
|
|
96
|
+
: index;
|
|
97
|
+
}
|
|
98
|
+
export function nearestPoint(
|
|
99
|
+
points: ChartPoint[],
|
|
100
|
+
time: number,
|
|
101
|
+
): ChartPoint | undefined {
|
|
102
|
+
return points[nearestIndex(points, time)];
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { init, use } from "echarts/core";
|
|
2
|
+
import { LineChart } from "echarts/charts";
|
|
3
|
+
import {
|
|
4
|
+
GridComponent,
|
|
5
|
+
TooltipComponent,
|
|
6
|
+
DataZoomComponent,
|
|
7
|
+
} from "echarts/components";
|
|
8
|
+
import { CanvasRenderer } from "echarts/renderers";
|
|
9
|
+
export function initCanvas(element: HTMLElement) {
|
|
10
|
+
// Registration belongs to the called function so bundlers cannot discard it as an unused module side effect.
|
|
11
|
+
use([
|
|
12
|
+
LineChart,
|
|
13
|
+
GridComponent,
|
|
14
|
+
TooltipComponent,
|
|
15
|
+
DataZoomComponent,
|
|
16
|
+
CanvasRenderer,
|
|
17
|
+
]);
|
|
18
|
+
return init(element, undefined, { renderer: "canvas" });
|
|
19
|
+
}
|