@procaaso/alphinity-ui-components 1.13.0 → 1.15.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/IMPLEMENTATION_GUIDE.md +1351 -1351
- package/README.md +98 -98
- package/dist/index.cjs +83 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -19
- package/dist/index.d.ts +54 -19
- package/dist/index.js +83 -52
- package/dist/index.js.map +1 -1
- package/package.json +85 -85
package/README.md
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
# @procaaso/alphinity-ui-components
|
|
2
|
-
|
|
3
|
-
A TypeScript React component library for Alphinity branded systems.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @procaaso/alphinity-ui-components
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```tsx
|
|
14
|
-
import { Button, ValueEntry, StatusIndicator } from '@procaaso/alphinity-ui-components';
|
|
15
|
-
import { useState } from 'react';
|
|
16
|
-
|
|
17
|
-
function HMIPanel() {
|
|
18
|
-
const [temperature, setTemperature] = useState(72.5);
|
|
19
|
-
const [pressure, setPressure] = useState(14.7);
|
|
20
|
-
const [systemStatus, setSystemStatus] = useState('normal');
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div className="p-4 space-y-4">
|
|
24
|
-
{/* System status indicators */}
|
|
25
|
-
<div className="flex gap-4 items-center">
|
|
26
|
-
<StatusIndicator status="normal" label="Power" size="medium" led={true} />
|
|
27
|
-
<StatusIndicator
|
|
28
|
-
status="alarm"
|
|
29
|
-
label="Temperature Alert"
|
|
30
|
-
animation="pulse"
|
|
31
|
-
shape="circle"
|
|
32
|
-
/>
|
|
33
|
-
<StatusIndicator status="warning" label="Maintenance Due" animation="blink" />
|
|
34
|
-
<StatusIndicator status="active" label="Process Running" size="large" />
|
|
35
|
-
{/* Use "unknown" when the backend does not trust the value, such as a bad-quality
|
|
36
|
-
OPC UA read. Do not use "fault": a fault means the device itself reports a problem. */}
|
|
37
|
-
<StatusIndicator status="unknown" label="Flow Sensor" />
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
{/* Process control values */}
|
|
41
|
-
<ValueEntry
|
|
42
|
-
value={temperature}
|
|
43
|
-
onChange={setTemperature}
|
|
44
|
-
label="Temperature"
|
|
45
|
-
unit="°C"
|
|
46
|
-
min={0}
|
|
47
|
-
max={100}
|
|
48
|
-
precision={1}
|
|
49
|
-
status="normal"
|
|
50
|
-
/>
|
|
51
|
-
|
|
52
|
-
<ValueEntry
|
|
53
|
-
value={pressure}
|
|
54
|
-
onChange={setPressure}
|
|
55
|
-
label="Pressure"
|
|
56
|
-
unit="PSI"
|
|
57
|
-
status="warning"
|
|
58
|
-
readOnly
|
|
59
|
-
/>
|
|
60
|
-
|
|
61
|
-
{/* Control buttons */}
|
|
62
|
-
<Button variant="primary" onClick={() => console.log('Start process')}>
|
|
63
|
-
Start
|
|
64
|
-
</Button>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Development
|
|
71
|
-
|
|
72
|
-
### Scripts
|
|
73
|
-
|
|
74
|
-
- `npm run build` - Build the library using tsup (ESM + CJS + DTS)
|
|
75
|
-
- `npm test` - Run tests with Vitest
|
|
76
|
-
- `npm run lint` - Lint code with ESLint
|
|
77
|
-
- `npm run typecheck` - Type check with TypeScript
|
|
78
|
-
- `npm run dev` - Run the demo playground on http://localhost:3010
|
|
79
|
-
|
|
80
|
-
### Demo views
|
|
81
|
-
|
|
82
|
-
The demo (`npm run dev`) mounts one of three views, selected by a `?demo=` query param:
|
|
83
|
-
|
|
84
|
-
- `http://localhost:3010/` *(default)* - **Runtime**: full P&ID canvas with live telemetry, simulation, and device control/config panels
|
|
85
|
-
- `http://localhost:3010/?demo=single-node` - **Single-node HMI mockup** over the full plant SVG layout
|
|
86
|
-
- `http://localhost:3010/?demo=gallery` - **Component gallery** of individual widgets
|
|
87
|
-
|
|
88
|
-
> The demo is served, not opened as a file — `demo-dist/index.html` references assets by absolute path and will be blank if opened via `file://`. Use `npm run dev`, or `npx vite preview --config vite.demo.config.ts` for the built version.
|
|
89
|
-
|
|
90
|
-
### Components
|
|
91
|
-
|
|
92
|
-
- **Button** - Control buttons with primary and secondary variants for industrial actions
|
|
93
|
-
- **ValueEntry** - Numeric/text input with validation, units, status indicators, and precision control
|
|
94
|
-
- **StatusIndicator** - LED-style status indicators with multiple states, animations, and customizable appearance
|
|
95
|
-
|
|
96
|
-
## Publishing
|
|
97
|
-
|
|
98
|
-
This package is published to the npm registry at https://www.npmjs.com/package/@procaaso/alphinity-ui-components
|
|
1
|
+
# @procaaso/alphinity-ui-components
|
|
2
|
+
|
|
3
|
+
A TypeScript React component library for Alphinity branded systems.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @procaaso/alphinity-ui-components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Button, ValueEntry, StatusIndicator } from '@procaaso/alphinity-ui-components';
|
|
15
|
+
import { useState } from 'react';
|
|
16
|
+
|
|
17
|
+
function HMIPanel() {
|
|
18
|
+
const [temperature, setTemperature] = useState(72.5);
|
|
19
|
+
const [pressure, setPressure] = useState(14.7);
|
|
20
|
+
const [systemStatus, setSystemStatus] = useState('normal');
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="p-4 space-y-4">
|
|
24
|
+
{/* System status indicators */}
|
|
25
|
+
<div className="flex gap-4 items-center">
|
|
26
|
+
<StatusIndicator status="normal" label="Power" size="medium" led={true} />
|
|
27
|
+
<StatusIndicator
|
|
28
|
+
status="alarm"
|
|
29
|
+
label="Temperature Alert"
|
|
30
|
+
animation="pulse"
|
|
31
|
+
shape="circle"
|
|
32
|
+
/>
|
|
33
|
+
<StatusIndicator status="warning" label="Maintenance Due" animation="blink" />
|
|
34
|
+
<StatusIndicator status="active" label="Process Running" size="large" />
|
|
35
|
+
{/* Use "unknown" when the backend does not trust the value, such as a bad-quality
|
|
36
|
+
OPC UA read. Do not use "fault": a fault means the device itself reports a problem. */}
|
|
37
|
+
<StatusIndicator status="unknown" label="Flow Sensor" />
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
{/* Process control values */}
|
|
41
|
+
<ValueEntry
|
|
42
|
+
value={temperature}
|
|
43
|
+
onChange={setTemperature}
|
|
44
|
+
label="Temperature"
|
|
45
|
+
unit="°C"
|
|
46
|
+
min={0}
|
|
47
|
+
max={100}
|
|
48
|
+
precision={1}
|
|
49
|
+
status="normal"
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<ValueEntry
|
|
53
|
+
value={pressure}
|
|
54
|
+
onChange={setPressure}
|
|
55
|
+
label="Pressure"
|
|
56
|
+
unit="PSI"
|
|
57
|
+
status="warning"
|
|
58
|
+
readOnly
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
{/* Control buttons */}
|
|
62
|
+
<Button variant="primary" onClick={() => console.log('Start process')}>
|
|
63
|
+
Start
|
|
64
|
+
</Button>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
### Scripts
|
|
73
|
+
|
|
74
|
+
- `npm run build` - Build the library using tsup (ESM + CJS + DTS)
|
|
75
|
+
- `npm test` - Run tests with Vitest
|
|
76
|
+
- `npm run lint` - Lint code with ESLint
|
|
77
|
+
- `npm run typecheck` - Type check with TypeScript
|
|
78
|
+
- `npm run dev` - Run the demo playground on http://localhost:3010
|
|
79
|
+
|
|
80
|
+
### Demo views
|
|
81
|
+
|
|
82
|
+
The demo (`npm run dev`) mounts one of three views, selected by a `?demo=` query param:
|
|
83
|
+
|
|
84
|
+
- `http://localhost:3010/` *(default)* - **Runtime**: full P&ID canvas with live telemetry, simulation, and device control/config panels
|
|
85
|
+
- `http://localhost:3010/?demo=single-node` - **Single-node HMI mockup** over the full plant SVG layout
|
|
86
|
+
- `http://localhost:3010/?demo=gallery` - **Component gallery** of individual widgets
|
|
87
|
+
|
|
88
|
+
> The demo is served, not opened as a file — `demo-dist/index.html` references assets by absolute path and will be blank if opened via `file://`. Use `npm run dev`, or `npx vite preview --config vite.demo.config.ts` for the built version.
|
|
89
|
+
|
|
90
|
+
### Components
|
|
91
|
+
|
|
92
|
+
- **Button** - Control buttons with primary and secondary variants for industrial actions
|
|
93
|
+
- **ValueEntry** - Numeric/text input with validation, units, status indicators, and precision control
|
|
94
|
+
- **StatusIndicator** - LED-style status indicators with multiple states, animations, and customizable appearance
|
|
95
|
+
|
|
96
|
+
## Publishing
|
|
97
|
+
|
|
98
|
+
This package is published to the npm registry at https://www.npmjs.com/package/@procaaso/alphinity-ui-components
|
package/dist/index.cjs
CHANGED
|
@@ -400,6 +400,13 @@ var ISA_UNKNOWN_DARK = {
|
|
|
400
400
|
bg: "#23263a",
|
|
401
401
|
border: "#6f74a3"
|
|
402
402
|
};
|
|
403
|
+
var FORCED_MARK_COLOR = {
|
|
404
|
+
fill: "#ede9fe",
|
|
405
|
+
stroke: "#5b21b6",
|
|
406
|
+
bg: "#ede9fe",
|
|
407
|
+
text: "#5b21b6",
|
|
408
|
+
border: "#7c3aed"
|
|
409
|
+
};
|
|
403
410
|
var DEFAULT_STATUS_COLORS = {
|
|
404
411
|
normal: { fill: "#888888", stroke: "#6b6b6b", bg: "#888888", text: "#ffffff", border: "#6b6b6b" },
|
|
405
412
|
warning: { fill: "#f59e0b", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
|
|
@@ -408,14 +415,8 @@ var DEFAULT_STATUS_COLORS = {
|
|
|
408
415
|
off: { fill: "#edeeef", stroke: "#000000", bg: "#666666", text: "#ffffff", border: "#4b5563" },
|
|
409
416
|
starting: { fill: "#90EE90", stroke: "#22c55e", bg: "#90EE90", text: "#000000", border: "#22c55e" },
|
|
410
417
|
stopping: { fill: "#FFD700", stroke: "#d97706", bg: "#FFD700", text: "#000000", border: "#d97706" },
|
|
411
|
-
unknown: { fill: ISA_UNKNOWN.bg, stroke: ISA_UNKNOWN.border, bg: ISA_UNKNOWN.bg, text: ISA_UNKNOWN.fg, border: ISA_UNKNOWN.border }
|
|
412
|
-
|
|
413
|
-
var FORCED_MARK_COLOR = {
|
|
414
|
-
fill: "#ede9fe",
|
|
415
|
-
stroke: "#5b21b6",
|
|
416
|
-
bg: "#ede9fe",
|
|
417
|
-
text: "#5b21b6",
|
|
418
|
-
border: "#7c3aed"
|
|
418
|
+
unknown: { fill: ISA_UNKNOWN.bg, stroke: ISA_UNKNOWN.border, bg: ISA_UNKNOWN.bg, text: ISA_UNKNOWN.fg, border: ISA_UNKNOWN.border },
|
|
419
|
+
forced: FORCED_MARK_COLOR
|
|
419
420
|
};
|
|
420
421
|
var STATUS_ALIASES = {
|
|
421
422
|
running: "normal",
|
|
@@ -1261,6 +1262,10 @@ var NodeControlsPanel = ({
|
|
|
1261
1262
|
// src/components/DeviceControlPanel/DeviceControlPanel.tsx
|
|
1262
1263
|
var import_react6 = __toESM(require("react"), 1);
|
|
1263
1264
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1265
|
+
var DISABLED_BUTTON_BG = "#e5e7eb";
|
|
1266
|
+
var DISABLED_BUTTON_TEXT = "#4b5563";
|
|
1267
|
+
var DISABLED_BUTTON_BORDER = "#6b7280";
|
|
1268
|
+
var DISABLED_BUTTON_HATCH = "repeating-linear-gradient(135deg, rgba(0,0,0,0.10) 0px, rgba(0,0,0,0.10) 4px, transparent 4px, transparent 10px)";
|
|
1264
1269
|
function DeviceControlPanelImpl({
|
|
1265
1270
|
binding,
|
|
1266
1271
|
onClose,
|
|
@@ -1341,6 +1346,36 @@ function DeviceControlPanelImpl({
|
|
|
1341
1346
|
const statusEntry = resolveStatusColor(binding.state.status, binding.theme?.statusColors);
|
|
1342
1347
|
const statusColor = statusEntry.bg;
|
|
1343
1348
|
const isUnknownStatus = normalizeDeviceStatus(binding.state.status) === "unknown";
|
|
1349
|
+
const startStopRowShown = binding.display?.showStartStop !== false && (activeCapabilities?.canStart === true || activeCapabilities?.canStop === true);
|
|
1350
|
+
const startEnabledNow = activeCapabilities?.canStart === true && activeCapabilities?.startEnabled !== false && !startPending;
|
|
1351
|
+
const stopEnabledNow = activeCapabilities?.canStop === true && activeCapabilities?.stopEnabled !== false;
|
|
1352
|
+
const startStopFontPx = Math.max(18, Math.round(18 * fontScale));
|
|
1353
|
+
const startStopButtonStyle = (enabled, enabledBg) => ({
|
|
1354
|
+
flex: 1,
|
|
1355
|
+
minWidth: 0,
|
|
1356
|
+
height: Math.max(touchSize, 56),
|
|
1357
|
+
borderRadius: "6px",
|
|
1358
|
+
fontSize: `${startStopFontPx}px`,
|
|
1359
|
+
fontWeight: 700,
|
|
1360
|
+
fontFamily: "Arial, sans-serif",
|
|
1361
|
+
letterSpacing: "0.5px",
|
|
1362
|
+
...enabled ? {
|
|
1363
|
+
backgroundColor: enabledBg,
|
|
1364
|
+
color: "#ffffff",
|
|
1365
|
+
border: "2px solid transparent",
|
|
1366
|
+
cursor: "pointer",
|
|
1367
|
+
opacity: 1
|
|
1368
|
+
} : {
|
|
1369
|
+
// Disabled look that does not depend on colour (ISA-101): a dashed
|
|
1370
|
+
// border and a diagonal hatch. A grey fill alone is not enough.
|
|
1371
|
+
backgroundColor: DISABLED_BUTTON_BG,
|
|
1372
|
+
backgroundImage: DISABLED_BUTTON_HATCH,
|
|
1373
|
+
color: DISABLED_BUTTON_TEXT,
|
|
1374
|
+
border: `2px dashed ${DISABLED_BUTTON_BORDER}`,
|
|
1375
|
+
cursor: "not-allowed",
|
|
1376
|
+
opacity: 1
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1344
1379
|
const handleModeChange = (mode) => {
|
|
1345
1380
|
onModeChange?.(mode);
|
|
1346
1381
|
};
|
|
@@ -1526,6 +1561,7 @@ function DeviceControlPanelImpl({
|
|
|
1526
1561
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1527
1562
|
"div",
|
|
1528
1563
|
{
|
|
1564
|
+
"data-testid": "device-status-dot",
|
|
1529
1565
|
style: {
|
|
1530
1566
|
width: "10px",
|
|
1531
1567
|
height: "10px",
|
|
@@ -2111,50 +2147,45 @@ function DeviceControlPanelImpl({
|
|
|
2111
2147
|
param.id
|
|
2112
2148
|
))
|
|
2113
2149
|
] }),
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
},
|
|
2154
|
-
children: stopPending ? "STOPPING\u2026" : "STOP"
|
|
2155
|
-
}
|
|
2156
|
-
)
|
|
2157
|
-
] }),
|
|
2150
|
+
startStopRowShown && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2151
|
+
"div",
|
|
2152
|
+
{
|
|
2153
|
+
"data-testid": "start-stop-row",
|
|
2154
|
+
style: { display: "flex", gap: spacing.gap, marginBottom: spacing.marginBottom },
|
|
2155
|
+
children: [
|
|
2156
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2157
|
+
"button",
|
|
2158
|
+
{
|
|
2159
|
+
type: "button",
|
|
2160
|
+
"data-control": "start",
|
|
2161
|
+
"data-enabled": startEnabledNow ? "true" : "false",
|
|
2162
|
+
onClick: () => {
|
|
2163
|
+
if (startEnabledNow) onStart?.();
|
|
2164
|
+
},
|
|
2165
|
+
disabled: !startEnabledNow,
|
|
2166
|
+
"aria-busy": startPending || void 0,
|
|
2167
|
+
style: startStopButtonStyle(startEnabledNow, "#10b981"),
|
|
2168
|
+
children: startPending ? "STARTING\u2026" : "START"
|
|
2169
|
+
}
|
|
2170
|
+
),
|
|
2171
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2172
|
+
"button",
|
|
2173
|
+
{
|
|
2174
|
+
type: "button",
|
|
2175
|
+
"data-control": "stop",
|
|
2176
|
+
"data-enabled": stopEnabledNow ? "true" : "false",
|
|
2177
|
+
onClick: () => {
|
|
2178
|
+
if (stopEnabledNow) onStop?.();
|
|
2179
|
+
},
|
|
2180
|
+
disabled: !stopEnabledNow,
|
|
2181
|
+
"aria-busy": stopPending || void 0,
|
|
2182
|
+
style: startStopButtonStyle(stopEnabledNow, "#ef4444"),
|
|
2183
|
+
children: stopPending ? "STOPPING\u2026" : "STOP"
|
|
2184
|
+
}
|
|
2185
|
+
)
|
|
2186
|
+
]
|
|
2187
|
+
}
|
|
2188
|
+
),
|
|
2158
2189
|
activeCapabilities?.customActions && activeCapabilities.customActions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: "8px" }, children: activeCapabilities.customActions.map((action) => {
|
|
2159
2190
|
const actionDisabled = action.disabled === true;
|
|
2160
2191
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|