@procaaso/alphinity-ui-components 1.0.0 → 1.0.1

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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/Button.tsx
1
+ // src/components/Button/Button.tsx
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  var Button = ({
4
4
  variant = "primary",
@@ -6,14 +6,163 @@ var Button = ({
6
6
  className = "",
7
7
  ...props
8
8
  }) => {
9
- const baseStyles = "h-20 min-w-[160px] px-12 py-5 rounded-3xl font-bold text-2xl transition-all duration-300 transform hover:scale-[1.03] active:scale-[0.97] focus:outline-none focus:ring-4 focus:ring-offset-3 focus:ring-offset-slate-900 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none shadow-2xl backdrop-blur-sm";
10
- const variantStyles = variant === "primary" ? "bg-gradient-to-br from-blue-400 via-blue-600 to-purple-700 hover:from-blue-500 hover:via-blue-700 hover:to-purple-800 text-white shadow-blue-600/40 focus:ring-blue-400/70 border border-blue-300/40 hover:shadow-2xl hover:shadow-blue-600/60 hover:border-blue-200/50" : "bg-gradient-to-br from-slate-500 via-slate-700 to-slate-900 hover:from-slate-400 hover:via-slate-600 hover:to-slate-800 text-slate-100 shadow-slate-600/40 focus:ring-slate-400/70 border border-slate-400/40 hover:shadow-2xl hover:shadow-slate-600/60 hover:border-slate-300/50";
11
- return /* @__PURE__ */ jsx("button", { className: `${baseStyles} ${variantStyles} ${className}`.trim(), ...props, children });
9
+ const baseClass = "ui-btn";
10
+ const variantClass = variant ? `ui-btn--${variant}` : "";
11
+ return /* @__PURE__ */ jsx("button", { className: `${baseClass} ${variantClass} ${className}`.trim(), ...props, children });
12
12
  };
13
13
 
14
- // src/TestValueEntry.tsx
15
- import { useState, useCallback } from "react";
14
+ // src/components/ControlPanel/ControlPanel.tsx
15
+ import { useState as useState4 } from "react";
16
+
17
+ // src/components/Selector/Selector.tsx
18
+ import { useState, useRef, useEffect } from "react";
16
19
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
20
+ var Selector = ({
21
+ value,
22
+ onChange,
23
+ options,
24
+ placeholder = "Select option...",
25
+ disabled = false,
26
+ status = "normal",
27
+ size = "medium",
28
+ label,
29
+ dropdownUp: _dropdownUp = false,
30
+ className = "",
31
+ ...props
32
+ }) => {
33
+ const [isOpen, setIsOpen] = useState(false);
34
+ const [focusedIndex, setFocusedIndex] = useState(-1);
35
+ const selectorRef = useRef(null);
36
+ const dropdownRef = useRef(null);
37
+ useEffect(() => {
38
+ const handleClickOutside = (event) => {
39
+ if (selectorRef.current && !selectorRef.current.contains(event.target)) {
40
+ setIsOpen(false);
41
+ setFocusedIndex(-1);
42
+ }
43
+ };
44
+ document.addEventListener("mousedown", handleClickOutside);
45
+ return () => document.removeEventListener("mousedown", handleClickOutside);
46
+ }, []);
47
+ const handleKeyDown = (e) => {
48
+ if (disabled) return;
49
+ switch (e.key) {
50
+ case "Enter":
51
+ case " ":
52
+ e.preventDefault();
53
+ if (!isOpen) {
54
+ setIsOpen(true);
55
+ setFocusedIndex(
56
+ Math.max(
57
+ 0,
58
+ options.findIndex((opt) => opt.value === value)
59
+ )
60
+ );
61
+ } else if (focusedIndex >= 0) {
62
+ const selectedOption2 = options[focusedIndex];
63
+ if (!selectedOption2.disabled) {
64
+ onChange(selectedOption2.value);
65
+ setIsOpen(false);
66
+ setFocusedIndex(-1);
67
+ }
68
+ }
69
+ break;
70
+ case "Escape":
71
+ setIsOpen(false);
72
+ setFocusedIndex(-1);
73
+ break;
74
+ case "ArrowDown":
75
+ e.preventDefault();
76
+ if (!isOpen) {
77
+ setIsOpen(true);
78
+ setFocusedIndex(
79
+ Math.max(
80
+ 0,
81
+ options.findIndex((opt) => opt.value === value)
82
+ )
83
+ );
84
+ } else {
85
+ setFocusedIndex((prev) => {
86
+ const nextIndex = Math.min(prev + 1, options.length - 1);
87
+ return options[nextIndex]?.disabled ? prev : nextIndex;
88
+ });
89
+ }
90
+ break;
91
+ case "ArrowUp":
92
+ e.preventDefault();
93
+ if (isOpen) {
94
+ setFocusedIndex((prev) => {
95
+ const nextIndex = Math.max(prev - 1, 0);
96
+ return options[nextIndex]?.disabled ? prev : nextIndex;
97
+ });
98
+ }
99
+ break;
100
+ }
101
+ };
102
+ const baseClass = "ui-selector";
103
+ const statusClass = status ? `ui-selector--${status}` : "";
104
+ const sizeClass = size ? `ui-selector--${size}` : "";
105
+ const selectedOption = options.find((opt) => opt.value === value);
106
+ return /* @__PURE__ */ jsxs("div", { className: `ui-selector-container ${className}`.trim(), children: [
107
+ label && /* @__PURE__ */ jsx2("label", { className: "ui-selector-label", children: label }),
108
+ /* @__PURE__ */ jsxs("div", { ref: selectorRef, style: { position: "relative" }, children: [
109
+ /* @__PURE__ */ jsxs(
110
+ "div",
111
+ {
112
+ className: `${baseClass} ${statusClass} ${sizeClass}`.trim(),
113
+ onClick: () => !disabled && setIsOpen(!isOpen),
114
+ onKeyDown: handleKeyDown,
115
+ tabIndex: disabled ? -1 : 0,
116
+ role: "combobox",
117
+ "aria-expanded": isOpen,
118
+ "aria-haspopup": "listbox",
119
+ ...props,
120
+ children: [
121
+ /* @__PURE__ */ jsx2("span", { className: "ui-selector-value", children: selectedOption ? selectedOption.label : placeholder }),
122
+ /* @__PURE__ */ jsx2("svg", { className: "ui-selector-arrow", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })
123
+ ]
124
+ }
125
+ ),
126
+ isOpen && /* @__PURE__ */ jsx2("div", { ref: dropdownRef, className: "ui-selector-dropdown", role: "listbox", children: options.map((option, index) => /* @__PURE__ */ jsxs(
127
+ "div",
128
+ {
129
+ className: `ui-selector-option${option.disabled ? " is-disabled" : ""}${option.value === value ? " is-selected" : ""}`,
130
+ onClick: () => {
131
+ if (!option.disabled) {
132
+ onChange(option.value);
133
+ setIsOpen(false);
134
+ setFocusedIndex(-1);
135
+ }
136
+ },
137
+ onMouseEnter: () => !option.disabled && setFocusedIndex(index),
138
+ role: "option",
139
+ "aria-selected": option.value === value,
140
+ "aria-disabled": option.disabled,
141
+ title: option.description,
142
+ children: [
143
+ /* @__PURE__ */ jsxs("div", { className: "ui-selector-option-label", children: [
144
+ /* @__PURE__ */ jsx2("span", { children: option.label }),
145
+ option.value === value && /* @__PURE__ */ jsx2("svg", { className: "ui-selector-check", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx2(
146
+ "path",
147
+ {
148
+ fillRule: "evenodd",
149
+ d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
150
+ clipRule: "evenodd"
151
+ }
152
+ ) })
153
+ ] }),
154
+ option.description && /* @__PURE__ */ jsx2("div", { className: "ui-selector-option-desc", children: option.description })
155
+ ]
156
+ },
157
+ option.value
158
+ )) })
159
+ ] })
160
+ ] });
161
+ };
162
+
163
+ // src/components/ValueEntry/ValueEntry.tsx
164
+ import { useState as useState2, useCallback } from "react";
165
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
17
166
  var ValueEntry = ({
18
167
  value,
19
168
  onChange,
@@ -29,8 +178,8 @@ var ValueEntry = ({
29
178
  disabled,
30
179
  ...props
31
180
  }) => {
32
- const [displayValue, setDisplayValue] = useState(String(value));
33
- const [isEditing, setIsEditing] = useState(false);
181
+ const [displayValue, setDisplayValue] = useState2(String(value));
182
+ const [isEditing, setIsEditing] = useState2(false);
34
183
  const handleFocus = useCallback(() => {
35
184
  setIsEditing(true);
36
185
  setDisplayValue(String(value));
@@ -71,6 +220,96 @@ var ValueEntry = ({
71
220
  },
72
221
  [value]
73
222
  );
223
+ const baseClass = "ui-value-entry";
224
+ const unitClass = unit ? "ui-value-entry--with-unit" : "";
225
+ const currentStatus = disabled || readOnly ? "disabled" : status;
226
+ const statusVariantClass = currentStatus ? `ui-value-entry--${currentStatus}` : "";
227
+ const displayText = isEditing ? displayValue : inputType === "numeric" ? Number(value).toFixed(precision) : String(value);
228
+ return /* @__PURE__ */ jsxs2("div", { className: "ui-value-entry-container", children: [
229
+ label && /* @__PURE__ */ jsx3("label", { className: "ui-value-entry-label", children: label }),
230
+ /* @__PURE__ */ jsxs2("div", { className: "ui-value-entry-wrapper", children: [
231
+ /* @__PURE__ */ jsx3(
232
+ "input",
233
+ {
234
+ type: inputType === "numeric" ? "text" : "text",
235
+ value: displayText,
236
+ onChange: handleChange,
237
+ onFocus: handleFocus,
238
+ onBlur: handleBlur,
239
+ onKeyDown: handleKeyDown,
240
+ disabled,
241
+ readOnly,
242
+ className: `${baseClass} ${statusVariantClass} ${unitClass} ${className}`.trim(),
243
+ ...props
244
+ }
245
+ ),
246
+ unit && /* @__PURE__ */ jsx3("span", { className: "ui-value-entry-unit", children: unit }),
247
+ status === "alarm" && /* @__PURE__ */ jsx3("div", { className: "ui-value-entry-badge ui-value-entry-badge--alarm" }),
248
+ status === "warning" && /* @__PURE__ */ jsx3("div", { className: "ui-value-entry-badge ui-value-entry-badge--warning" })
249
+ ] })
250
+ ] });
251
+ };
252
+
253
+ // src/components/ValueEntry/LegacyValueEntry.tsx
254
+ import { useState as useState3, useCallback as useCallback2 } from "react";
255
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
256
+ var LegacyValueEntry = ({
257
+ value,
258
+ onChange,
259
+ unit,
260
+ status = "normal",
261
+ min,
262
+ max,
263
+ precision = 2,
264
+ inputType = "numeric",
265
+ label,
266
+ readOnly = false,
267
+ className = "",
268
+ disabled,
269
+ ...props
270
+ }) => {
271
+ const [displayValue, setDisplayValue] = useState3(String(value));
272
+ const [isEditing, setIsEditing] = useState3(false);
273
+ const handleFocus = useCallback2(() => {
274
+ setIsEditing(true);
275
+ setDisplayValue(String(value));
276
+ }, [value]);
277
+ const handleBlur = useCallback2(() => {
278
+ setIsEditing(false);
279
+ let newValue = displayValue;
280
+ if (inputType === "numeric") {
281
+ const numValue = parseFloat(displayValue);
282
+ if (!isNaN(numValue)) {
283
+ let constrainedValue = numValue;
284
+ if (min !== void 0) constrainedValue = Math.max(min, constrainedValue);
285
+ if (max !== void 0) constrainedValue = Math.min(max, constrainedValue);
286
+ constrainedValue = parseFloat(constrainedValue.toFixed(precision));
287
+ newValue = constrainedValue;
288
+ setDisplayValue(String(constrainedValue));
289
+ } else {
290
+ setDisplayValue(String(value));
291
+ return;
292
+ }
293
+ }
294
+ if (newValue !== value) {
295
+ onChange(newValue);
296
+ }
297
+ }, [displayValue, inputType, min, max, precision, value, onChange]);
298
+ const handleChange = useCallback2((e) => {
299
+ setDisplayValue(e.target.value);
300
+ }, []);
301
+ const handleKeyDown = useCallback2(
302
+ (e) => {
303
+ if (e.key === "Enter") {
304
+ e.currentTarget.blur();
305
+ }
306
+ if (e.key === "Escape") {
307
+ setDisplayValue(String(value));
308
+ e.currentTarget.blur();
309
+ }
310
+ },
311
+ [value]
312
+ );
74
313
  const displayText = isEditing ? displayValue : inputType === "numeric" ? Number(value).toFixed(precision) : String(value);
75
314
  const getStatusStyles = () => {
76
315
  switch (status) {
@@ -143,7 +382,7 @@ var ValueEntry = ({
143
382
  zIndex: 10,
144
383
  boxShadow: "0 2px 8px rgba(59, 130, 246, 0.4)"
145
384
  };
146
- return /* @__PURE__ */ jsxs(
385
+ return /* @__PURE__ */ jsxs3(
147
386
  "div",
148
387
  {
149
388
  style: {
@@ -153,7 +392,7 @@ var ValueEntry = ({
153
392
  gap: "0.75rem"
154
393
  },
155
394
  children: [
156
- label && /* @__PURE__ */ jsx2(
395
+ label && /* @__PURE__ */ jsx4(
157
396
  "label",
158
397
  {
159
398
  style: {
@@ -166,8 +405,8 @@ var ValueEntry = ({
166
405
  children: label
167
406
  }
168
407
  ),
169
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", display: "inline-flex", alignItems: "center" }, children: [
170
- /* @__PURE__ */ jsx2(
408
+ /* @__PURE__ */ jsxs3("div", { style: { position: "relative", display: "inline-flex", alignItems: "center" }, children: [
409
+ /* @__PURE__ */ jsx4(
171
410
  "input",
172
411
  {
173
412
  type: "text",
@@ -188,8 +427,8 @@ var ValueEntry = ({
188
427
  ...props
189
428
  }
190
429
  ),
191
- unit && /* @__PURE__ */ jsx2("span", { style: unitStyle, children: unit }),
192
- status === "alarm" && /* @__PURE__ */ jsx2(
430
+ unit && /* @__PURE__ */ jsx4("span", { style: unitStyle, children: unit }),
431
+ status === "alarm" && /* @__PURE__ */ jsx4(
193
432
  "div",
194
433
  {
195
434
  style: {
@@ -205,7 +444,7 @@ var ValueEntry = ({
205
444
  }
206
445
  }
207
446
  ),
208
- status === "warning" && /* @__PURE__ */ jsx2(
447
+ status === "warning" && /* @__PURE__ */ jsx4(
209
448
  "div",
210
449
  {
211
450
  style: {
@@ -226,8 +465,174 @@ var ValueEntry = ({
226
465
  );
227
466
  };
228
467
 
229
- // src/StatusIndicator.tsx
230
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
468
+ // src/components/ControlPanel/ControlPanel.tsx
469
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
470
+ var ControlPanel = ({
471
+ title,
472
+ selectedMode,
473
+ modeOptions,
474
+ onModeChange,
475
+ setpointValue,
476
+ onSetpointChange,
477
+ secondSetpointValue,
478
+ onSecondSetpointChange,
479
+ modeConfigs = {},
480
+ defaultUnit = "Units",
481
+ defaultLabel = "Setpoint",
482
+ defaultMin = 0,
483
+ defaultMax = 100,
484
+ defaultPrecision = 2,
485
+ status = "normal",
486
+ collapsed = false,
487
+ onCollapseChange,
488
+ size = "medium",
489
+ setpointReadOnly = false,
490
+ showStartStopButtons = false,
491
+ isRunning = false,
492
+ onStart,
493
+ onStop,
494
+ startButtonText = "Start",
495
+ stopButtonText = "Stop",
496
+ className = "",
497
+ ...props
498
+ }) => {
499
+ const [isExpanded, setIsExpanded] = useState4(!collapsed);
500
+ const toggleExpanded = () => {
501
+ const newExpanded = !isExpanded;
502
+ setIsExpanded(newExpanded);
503
+ onCollapseChange?.(!newExpanded);
504
+ };
505
+ const currentModeConfig = modeConfigs[selectedMode] || {};
506
+ const currentUnit = currentModeConfig.unit || defaultUnit;
507
+ const currentLabel = currentModeConfig.label || defaultLabel;
508
+ const currentMin = currentModeConfig.min ?? defaultMin;
509
+ const currentMax = currentModeConfig.max ?? defaultMax;
510
+ const currentPrecision = currentModeConfig.precision ?? defaultPrecision;
511
+ const hasSecondSetpoint = currentModeConfig.hasSecondSetpoint || false;
512
+ const baseClass = "ui-control-panel";
513
+ const statusClass = status ? `ui-control-panel--${status}` : "";
514
+ const sizeClass = size ? `ui-control-panel--${size}` : "";
515
+ const expandedClass = isExpanded ? "is-expanded" : "is-collapsed";
516
+ const collapsibleClass = onCollapseChange ? "is-collapsible" : "";
517
+ return /* @__PURE__ */ jsxs4(
518
+ "div",
519
+ {
520
+ className: `${baseClass} ${statusClass} ${sizeClass} ${expandedClass} ${collapsibleClass} ${className}`.trim(),
521
+ ...props,
522
+ children: [
523
+ /* @__PURE__ */ jsx5("div", { className: `ui-control-panel-badge ui-control-panel-badge--${status}` }),
524
+ /* @__PURE__ */ jsxs4(
525
+ "div",
526
+ {
527
+ className: "ui-control-panel-header",
528
+ onClick: onCollapseChange ? toggleExpanded : void 0,
529
+ children: [
530
+ /* @__PURE__ */ jsx5("h3", { className: "ui-control-panel-title", children: title }),
531
+ onCollapseChange && /* @__PURE__ */ jsx5(
532
+ "svg",
533
+ {
534
+ className: "ui-control-panel-chevron",
535
+ fill: "none",
536
+ viewBox: "0 0 24 24",
537
+ stroke: "currentColor",
538
+ children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" })
539
+ }
540
+ )
541
+ ]
542
+ }
543
+ ),
544
+ /* @__PURE__ */ jsxs4("div", { className: "ui-control-panel-content", children: [
545
+ /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
546
+ Selector,
547
+ {
548
+ value: selectedMode,
549
+ onChange: onModeChange,
550
+ options: modeOptions,
551
+ label: "Control Mode",
552
+ status,
553
+ size: size === "small" ? "small" : size === "large" ? "large" : "medium",
554
+ disabled: status === "disabled"
555
+ }
556
+ ) }),
557
+ /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
558
+ LegacyValueEntry,
559
+ {
560
+ value: setpointValue,
561
+ onChange: onSetpointChange,
562
+ label: currentLabel,
563
+ unit: currentUnit,
564
+ min: currentMin,
565
+ max: currentMax,
566
+ precision: currentPrecision,
567
+ status,
568
+ readOnly: setpointReadOnly || status === "disabled",
569
+ disabled: status === "disabled"
570
+ }
571
+ ) }),
572
+ hasSecondSetpoint && onSecondSetpointChange && /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
573
+ LegacyValueEntry,
574
+ {
575
+ value: secondSetpointValue || 0,
576
+ onChange: onSecondSetpointChange,
577
+ label: currentModeConfig.secondLabel || "Time",
578
+ unit: currentModeConfig.secondUnit || "sec",
579
+ min: currentModeConfig.secondMin ?? 0,
580
+ max: currentModeConfig.secondMax ?? 999,
581
+ precision: currentModeConfig.secondPrecision ?? 0,
582
+ status,
583
+ readOnly: setpointReadOnly || status === "disabled",
584
+ disabled: status === "disabled"
585
+ }
586
+ ) }),
587
+ showStartStopButtons && (onStart || onStop) && /* @__PURE__ */ jsxs4("div", { className: "ui-control-panel-buttons", children: [
588
+ onStart && /* @__PURE__ */ jsx5(
589
+ Button,
590
+ {
591
+ variant: "primary",
592
+ onClick: onStart,
593
+ disabled: isRunning || status === "disabled",
594
+ className: isRunning ? "is-running" : "",
595
+ children: startButtonText
596
+ }
597
+ ),
598
+ onStop && /* @__PURE__ */ jsx5(
599
+ Button,
600
+ {
601
+ variant: "secondary",
602
+ onClick: onStop,
603
+ disabled: !isRunning || status === "disabled",
604
+ className: `ui-control-panel-stop-btn${!isRunning ? " is-disabled" : ""}`,
605
+ children: stopButtonText
606
+ }
607
+ )
608
+ ] }),
609
+ /* @__PURE__ */ jsxs4("div", { className: "ui-control-panel-info", children: [
610
+ /* @__PURE__ */ jsxs4("span", { children: [
611
+ "Mode: ",
612
+ selectedMode.toUpperCase()
613
+ ] }),
614
+ /* @__PURE__ */ jsxs4("div", { className: "ui-control-panel-info-values", children: [
615
+ /* @__PURE__ */ jsxs4("span", { children: [
616
+ "SP: ",
617
+ String(setpointValue),
618
+ currentUnit
619
+ ] }),
620
+ hasSecondSetpoint && secondSetpointValue && /* @__PURE__ */ jsxs4("span", { children: [
621
+ currentModeConfig.secondLabel,
622
+ ": ",
623
+ String(secondSetpointValue),
624
+ currentModeConfig.secondUnit
625
+ ] })
626
+ ] })
627
+ ] })
628
+ ] })
629
+ ]
630
+ }
631
+ );
632
+ };
633
+
634
+ // src/components/StatusIndicator/StatusIndicator.tsx
635
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
231
636
  var StatusIndicator = ({
232
637
  status,
233
638
  size = "medium",
@@ -239,701 +644,929 @@ var StatusIndicator = ({
239
644
  className = "",
240
645
  ...props
241
646
  }) => {
242
- const sizeStyles = {
243
- small: "w-5 h-5",
244
- medium: "w-7 h-7",
245
- large: "w-10 h-10"
246
- };
247
- const shapeStyles = {
248
- circle: "rounded-full",
249
- square: "rounded-lg",
250
- diamond: "rotate-45 rounded-lg"
251
- };
252
- const statusStyles = {
253
- off: "bg-gradient-to-br from-slate-500 to-slate-600 border-slate-400 shadow-slate-500/30",
254
- normal: "bg-gradient-to-br from-emerald-400 via-green-500 to-green-600 border-emerald-300 shadow-green-500/70",
255
- alarm: "bg-gradient-to-br from-red-400 via-red-500 to-red-600 border-red-300 shadow-red-500/70",
256
- warning: "bg-gradient-to-br from-amber-400 via-yellow-500 to-orange-500 border-amber-300 shadow-yellow-500/70",
257
- active: "bg-gradient-to-br from-blue-400 via-blue-500 to-indigo-600 border-blue-300 shadow-blue-500/70",
258
- fault: "bg-gradient-to-br from-purple-400 via-purple-500 to-violet-600 border-purple-300 shadow-purple-500/70"
259
- };
260
- const animationStyles = {
261
- none: "",
262
- pulse: "animate-pulse",
263
- blink: "animate-bounce",
264
- flash: "animate-ping"
265
- };
266
- const ledEffect = led ? "shadow-lg border-2" : "border";
267
- const glowEffect = status !== "off" && led ? "shadow-lg" : "";
268
- const indicatorClasses = `
269
- ${sizeStyles[size]}
270
- ${shapeStyles[shape]}
271
- ${statusStyles[status]}
272
- ${animationStyles[animation]}
273
- ${ledEffect}
274
- ${glowEffect}
275
- inline-block
276
- transition-all
277
- duration-300
278
- `.trim().replace(/\s+/g, " ");
279
- const labelClasses = "text-sm font-medium text-gray-700 uppercase tracking-wide select-none";
647
+ const baseClass = "ui-status-indicator";
648
+ const statusClass = status ? `ui-status-indicator--${status}` : "";
649
+ const sizeClass = size ? `ui-status-indicator--${size}` : "";
650
+ const shapeClass = shape ? `ui-status-indicator--${shape}` : "";
651
+ const animationClass = animation ? `ui-status-indicator--${animation}` : "";
652
+ const ledClass = led ? "ui-status-indicator--led" : "";
280
653
  const getContainerLayout = () => {
281
654
  switch (labelPosition) {
282
655
  case "left":
283
- return "inline-flex items-center gap-2 flex-row-reverse";
656
+ return "ui-status-indicator-container ui-status-indicator-label-left";
284
657
  case "right":
285
- return "inline-flex items-center gap-2";
658
+ return "ui-status-indicator-container ui-status-indicator-label-right";
286
659
  case "top":
287
- return "inline-flex flex-col-reverse items-center gap-1";
660
+ return "ui-status-indicator-container ui-status-indicator-label-top";
288
661
  case "bottom":
289
- return "inline-flex flex-col items-center gap-1";
662
+ return "ui-status-indicator-container ui-status-indicator-label-bottom";
290
663
  default:
291
- return "inline-flex items-center gap-2";
292
- }
293
- };
294
- const getAriaLabel = () => {
295
- if (label) {
296
- return `${label}: ${status}`;
664
+ return "ui-status-indicator-container ui-status-indicator-label-right";
297
665
  }
298
- return `Status indicator: ${status}`;
299
- };
300
- const getAriaDescription = () => {
301
- const descriptions = {
302
- off: "Inactive or disabled state",
303
- normal: "Normal operating condition",
304
- alarm: "Critical alarm condition requiring attention",
305
- warning: "Warning condition that should be monitored",
306
- active: "Currently active or running",
307
- fault: "System fault or error condition"
308
- };
309
- return descriptions[status];
310
666
  };
311
- return /* @__PURE__ */ jsxs2("div", { className: `${getContainerLayout()} ${className}`.trim(), ...props, children: [
312
- /* @__PURE__ */ jsx3(
313
- "div",
667
+ return /* @__PURE__ */ jsxs5("span", { className: getContainerLayout(), children: [
668
+ label && (labelPosition === "left" || labelPosition === "top") && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-label", children: label }),
669
+ /* @__PURE__ */ jsx6(
670
+ "span",
314
671
  {
315
- className: indicatorClasses,
316
- role: "status",
317
- "aria-label": getAriaLabel(),
318
- "aria-description": getAriaDescription(),
319
- "data-status": status,
320
- "data-testid": `status-indicator-${status}`
672
+ className: `
673
+ ${baseClass}
674
+ ${statusClass}
675
+ ${sizeClass}
676
+ ${shapeClass}
677
+ ${animationClass}
678
+ ${ledClass}
679
+ ${className}
680
+ `.replace(/\s+/g, " ").trim(),
681
+ ...props
321
682
  }
322
683
  ),
323
- label && /* @__PURE__ */ jsx3("span", { className: labelClasses, children: label })
684
+ label && (labelPosition === "right" || labelPosition === "bottom") && /* @__PURE__ */ jsx6("span", { className: "ui-status-indicator-label", children: label })
324
685
  ] });
325
686
  };
326
687
 
327
- // src/Selector.tsx
328
- import { useState as useState2, useRef, useEffect } from "react";
329
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
330
- var Selector = ({
331
- value,
332
- onChange,
333
- options,
334
- placeholder = "Select option...",
335
- disabled = false,
336
- status = "normal",
337
- size = "medium",
338
- label,
339
- dropdownUp = false,
340
- className = "",
341
- ...props
342
- }) => {
343
- const [isOpen, setIsOpen] = useState2(false);
344
- const [focusedIndex, setFocusedIndex] = useState2(-1);
345
- const selectorRef = useRef(null);
346
- const dropdownRef = useRef(null);
347
- useEffect(() => {
348
- const handleClickOutside = (event) => {
349
- if (selectorRef.current && !selectorRef.current.contains(event.target)) {
350
- setIsOpen(false);
351
- setFocusedIndex(-1);
688
+ // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
689
+ import React5 from "react";
690
+ import { Tab } from "@mui/material";
691
+
692
+ // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.styles.tsx
693
+ import { Box, IconButton, Tabs } from "@mui/material";
694
+ import styled from "@emotion/styled";
695
+ import { jsx as jsx7 } from "react/jsx-runtime";
696
+ var FullscreenContainer = styled(Box)(
697
+ ({ leftCollapsed, rightCollapsed }) => {
698
+ const getGridTemplateAreas = () => {
699
+ if (leftCollapsed && rightCollapsed) {
700
+ return `"svg-area" "footer"`;
352
701
  }
702
+ if (leftCollapsed) {
703
+ return `"svg-area right-panel" "footer footer"`;
704
+ }
705
+ if (rightCollapsed) {
706
+ return `"left-panel svg-area" "footer footer"`;
707
+ }
708
+ return `"left-panel svg-area right-panel" "footer footer footer"`;
353
709
  };
354
- document.addEventListener("mousedown", handleClickOutside);
355
- return () => document.removeEventListener("mousedown", handleClickOutside);
356
- }, []);
357
- const handleKeyDown = (e) => {
358
- if (disabled) return;
359
- switch (e.key) {
360
- case "Enter":
361
- case " ":
362
- e.preventDefault();
363
- if (!isOpen) {
364
- setIsOpen(true);
365
- setFocusedIndex(
366
- Math.max(
367
- 0,
368
- options.findIndex((opt) => opt.value === value)
369
- )
370
- );
371
- } else if (focusedIndex >= 0) {
372
- const selectedOption2 = options[focusedIndex];
373
- if (!selectedOption2.disabled) {
374
- onChange(selectedOption2.value);
375
- setIsOpen(false);
376
- setFocusedIndex(-1);
377
- }
378
- }
379
- break;
380
- case "Escape":
381
- setIsOpen(false);
382
- setFocusedIndex(-1);
383
- break;
384
- case "ArrowDown":
385
- e.preventDefault();
386
- if (!isOpen) {
387
- setIsOpen(true);
388
- setFocusedIndex(
389
- Math.max(
390
- 0,
391
- options.findIndex((opt) => opt.value === value)
392
- )
393
- );
394
- } else {
395
- setFocusedIndex((prev) => {
396
- const nextIndex = Math.min(prev + 1, options.length - 1);
397
- return options[nextIndex]?.disabled ? prev : nextIndex;
398
- });
399
- }
400
- break;
401
- case "ArrowUp":
402
- e.preventDefault();
403
- if (isOpen) {
404
- setFocusedIndex((prev) => {
405
- const nextIndex = Math.max(prev - 1, 0);
406
- return options[nextIndex]?.disabled ? prev : nextIndex;
407
- });
408
- }
409
- break;
710
+ const getGridTemplateColumns = () => {
711
+ if (leftCollapsed && rightCollapsed) {
712
+ return "1fr";
713
+ }
714
+ if (leftCollapsed) {
715
+ return "1fr 380px";
716
+ }
717
+ if (rightCollapsed) {
718
+ return "420px 1fr";
719
+ }
720
+ return "420px 1fr 380px";
721
+ };
722
+ return {
723
+ width: "100vw",
724
+ height: "100vh",
725
+ background: "#f5f5f5",
726
+ display: "grid",
727
+ gridTemplateAreas: getGridTemplateAreas(),
728
+ gridTemplateRows: "1fr 100px",
729
+ gridTemplateColumns: getGridTemplateColumns(),
730
+ gap: "10px",
731
+ padding: "10px",
732
+ boxSizing: "border-box",
733
+ transition: "grid-template-columns 0.3s ease-in-out"
734
+ };
735
+ }
736
+ );
737
+ var LeftPanel = styled(Box)(({ collapsed }) => ({
738
+ gridArea: "left-panel",
739
+ background: "rgba(255, 255, 255, 0.0)",
740
+ border: "1px solid #ddd",
741
+ borderRadius: "8px",
742
+ padding: collapsed ? "0" : "15px",
743
+ overflowY: "auto",
744
+ display: collapsed ? "none" : "block",
745
+ transition: "all 0.3s ease-in-out"
746
+ }));
747
+ var RightPanel = styled(Box)(({ collapsed }) => ({
748
+ gridArea: "right-panel",
749
+ background: "rgba(255, 255, 255, 0.0)",
750
+ border: "1px solid #ddd",
751
+ borderRadius: "8px",
752
+ padding: collapsed ? "0" : "15px",
753
+ overflowY: "auto",
754
+ display: collapsed ? "none" : "block",
755
+ transition: "all 0.3s ease-in-out"
756
+ }));
757
+ var SVGArea = styled(Box)({
758
+ gridArea: "svg-area",
759
+ display: "flex",
760
+ alignItems: "center",
761
+ justifyContent: "flex-start",
762
+ position: "relative",
763
+ background: "rgba(255, 255, 255, 0.0)",
764
+ border: "1px solid #ddd",
765
+ borderRadius: "8px",
766
+ overflow: "auto",
767
+ width: "100%",
768
+ height: "100%"
769
+ });
770
+ var LeftToggleButton = styled(IconButton)(
771
+ ({ collapsed }) => ({
772
+ position: "absolute",
773
+ left: collapsed ? "10px" : "410px",
774
+ top: "50%",
775
+ transform: "translateY(-50%)",
776
+ zIndex: 1e3,
777
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
778
+ border: "1px solid #ddd",
779
+ transition: "left 0.3s ease-in-out",
780
+ "&:hover": {
781
+ backgroundColor: "rgba(255, 255, 255, 1)"
410
782
  }
411
- };
412
- const selectedOption = options.find((opt) => opt.value === value);
413
- const getSizeStyles = () => {
414
- switch (size) {
415
- case "small":
416
- return {
417
- height: "2.5rem",
418
- padding: "0.5rem 0.75rem",
419
- fontSize: "0.875rem",
420
- iconSize: "1rem"
421
- };
422
- case "large":
423
- return {
424
- height: "4rem",
425
- padding: "1rem 1.5rem",
426
- fontSize: "1.25rem",
427
- iconSize: "1.5rem"
428
- };
429
- default:
430
- return {
431
- height: "3rem",
432
- padding: "0.75rem 1rem",
433
- fontSize: "1rem",
434
- iconSize: "1.25rem"
435
- };
783
+ })
784
+ );
785
+ var RightToggleButton = styled(IconButton)(
786
+ ({ collapsed }) => ({
787
+ position: "absolute",
788
+ right: collapsed ? "10px" : "370px",
789
+ top: "50%",
790
+ transform: "translateY(-50%)",
791
+ zIndex: 1e3,
792
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
793
+ border: "1px solid #ddd",
794
+ transition: "right 0.3s ease-in-out",
795
+ "&:hover": {
796
+ backgroundColor: "rgba(255, 255, 255, 1)"
436
797
  }
437
- };
438
- const getStatusStyles = () => {
439
- switch (status) {
440
- case "alarm":
441
- return {
442
- borderColor: "var(--selector-alarm-border, #ef4444)",
443
- backgroundColor: "var(--selector-alarm-bg, rgba(239, 68, 68, 0.15))",
444
- color: "var(--selector-alarm-text, #fecaca)",
445
- boxShadow: "var(--selector-alarm-shadow, 0 0 20px rgba(239, 68, 68, 0.3))"
446
- };
447
- case "warning":
448
- return {
449
- borderColor: "var(--selector-warning-border, #f59e0b)",
450
- backgroundColor: "var(--selector-warning-bg, rgba(245, 158, 11, 0.15))",
451
- color: "var(--selector-warning-text, #fed7aa)",
452
- boxShadow: "var(--selector-warning-shadow, 0 0 20px rgba(245, 158, 11, 0.3))"
453
- };
454
- case "disabled":
455
- return {
456
- borderColor: "var(--selector-disabled-border, #6b7280)",
457
- backgroundColor: "var(--selector-disabled-bg, rgba(107, 114, 128, 0.1))",
458
- color: "var(--selector-disabled-text, #9ca3af)",
459
- cursor: "not-allowed"
460
- };
461
- default:
462
- return {
463
- borderColor: "var(--selector-normal-border, #64748b)",
464
- backgroundColor: "var(--selector-normal-bg, rgba(30, 41, 59, 0.9))",
465
- color: "var(--selector-normal-text, #f1f5f9)",
466
- boxShadow: "var(--selector-normal-shadow, 0 8px 32px rgba(0, 0, 0, 0.3))"
467
- };
798
+ })
799
+ );
800
+ var DisplayModeToggle = styled(IconButton)(({ mode }) => ({
801
+ position: "absolute",
802
+ top: "50px",
803
+ left: "75%",
804
+ transform: "translateX(-50%)",
805
+ zIndex: 1e3,
806
+ backgroundColor: mode === "dashboard" ? "rgba(52, 152, 219, 0.9)" : "rgba(255, 255, 255, 0.9)",
807
+ color: mode === "dashboard" ? "white" : "#333",
808
+ border: "1px solid #ddd",
809
+ borderRadius: "20px",
810
+ padding: "8px 16px",
811
+ fontSize: "12px",
812
+ fontWeight: "600",
813
+ minWidth: "120px",
814
+ transition: "all 0.3s ease-in-out",
815
+ "&:hover": {
816
+ backgroundColor: mode === "dashboard" ? "rgba(52, 152, 219, 1)" : "rgba(255, 255, 255, 1)",
817
+ transform: "translateX(-50%) scale(1.05)"
818
+ }
819
+ }));
820
+ var ChevronLeft = () => /* @__PURE__ */ jsx7(
821
+ "div",
822
+ {
823
+ style: {
824
+ width: 0,
825
+ height: 0,
826
+ borderTop: "6px solid transparent",
827
+ borderBottom: "6px solid transparent",
828
+ borderRight: "8px solid #666"
468
829
  }
469
- };
470
- const sizeStyles = getSizeStyles();
471
- const statusStyles = getStatusStyles();
472
- const selectorStyle = {
473
- position: "relative",
474
- width: "100%",
475
- height: sizeStyles.height,
476
- padding: sizeStyles.padding,
477
- paddingRight: "3rem",
478
- border: "2px solid",
479
- borderRadius: "1rem",
480
- fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
481
- fontSize: sizeStyles.fontSize,
482
- fontWeight: "bold",
483
- outline: "none",
484
- transition: "all 300ms ease",
485
- backdropFilter: "blur(8px)",
486
- cursor: disabled ? "not-allowed" : "pointer",
487
- display: "flex",
488
- alignItems: "center",
489
- justifyContent: "space-between",
490
- userSelect: "none",
491
- ...statusStyles
492
- };
493
- const dropdownStyle = {
830
+ }
831
+ );
832
+ var ChevronRight = () => /* @__PURE__ */ jsx7(
833
+ "div",
834
+ {
835
+ style: {
836
+ width: 0,
837
+ height: 0,
838
+ borderTop: "6px solid transparent",
839
+ borderBottom: "6px solid transparent",
840
+ borderLeft: "8px solid #666"
841
+ }
842
+ }
843
+ );
844
+ var ZoomControls = styled(Box)({
845
+ position: "absolute",
846
+ top: "10px",
847
+ right: "10px",
848
+ display: "flex",
849
+ flexDirection: "column",
850
+ gap: "5px",
851
+ zIndex: 1e3
852
+ });
853
+ var ZoomButton = styled(IconButton)({
854
+ backgroundColor: "rgba(255, 255, 255, 0.9)",
855
+ border: "1px solid #ddd",
856
+ width: "32px",
857
+ height: "32px",
858
+ fontSize: "14px",
859
+ fontWeight: "bold",
860
+ "&:hover": {
861
+ backgroundColor: "rgba(255, 255, 255, 1)"
862
+ }
863
+ });
864
+ var FooterPanel = styled(Box)({
865
+ gridArea: "footer",
866
+ background: "rgba(255, 255, 255, 0.95)",
867
+ border: "1px solid #ddd",
868
+ borderRadius: "8px",
869
+ padding: "10px",
870
+ display: "flex",
871
+ justifyContent: "space-around",
872
+ alignItems: "center"
873
+ });
874
+ var PanelTabs = styled(Tabs)({
875
+ minHeight: "36px",
876
+ "& .MuiTabs-indicator": {
877
+ backgroundColor: "#546e7a"
878
+ },
879
+ "& .MuiTab-root": {
880
+ minHeight: "36px",
881
+ padding: "6px 12px",
882
+ fontSize: "12px",
883
+ fontWeight: "600",
884
+ textTransform: "none",
885
+ color: "#666",
886
+ "&.Mui-selected": {
887
+ color: "#546e7a"
888
+ }
889
+ }
890
+ });
891
+ var PanelContent = styled(Box)({
892
+ flex: 1,
893
+ overflow: "auto",
894
+ padding: "10px"
895
+ });
896
+ var FixedSVGContainer = styled("div")({
897
+ position: "relative",
898
+ width: "1300px",
899
+ height: "400px",
900
+ minWidth: "1300px",
901
+ minHeight: "400px",
902
+ maxWidth: "1300px",
903
+ maxHeight: "400px",
904
+ border: "none",
905
+ backgroundColor: "transparent",
906
+ overflow: "visible",
907
+ "& .svg-locked-overlay": {
908
+ position: "absolute",
909
+ pointerEvents: "auto",
910
+ zIndex: 10
911
+ },
912
+ "& svg": {
494
913
  position: "absolute",
495
- top: dropdownUp ? "auto" : "100%",
496
- bottom: dropdownUp ? "100%" : "auto",
497
914
  left: 0,
498
- right: 0,
499
- marginTop: dropdownUp ? 0 : "0.25rem",
500
- marginBottom: dropdownUp ? "0.25rem" : 0,
501
- backgroundColor: "var(--selector-dropdown-bg, rgba(30, 41, 59, 0.95))",
502
- border: "2px solid var(--selector-dropdown-border, #64748b)",
503
- borderRadius: "1rem",
504
- backdropFilter: "blur(12px)",
505
- boxShadow: "var(--selector-dropdown-shadow, 0 12px 48px rgba(0, 0, 0, 0.4))",
506
- zIndex: 50,
507
- maxHeight: "12rem",
508
- overflowY: "auto"
509
- };
510
- const optionStyle = (option, index) => ({
511
- padding: sizeStyles.padding,
512
- fontSize: sizeStyles.fontSize,
513
- fontWeight: "600",
514
- color: option.disabled ? "var(--selector-option-disabled-text, #6b7280)" : "var(--selector-option-text, #f1f5f9)",
515
- backgroundColor: index === focusedIndex ? "var(--selector-option-focused-bg, rgba(59, 130, 246, 0.3))" : "transparent",
516
- cursor: option.disabled ? "not-allowed" : "pointer",
517
- transition: "all 150ms ease",
518
- borderRadius: index === focusedIndex ? "0.5rem" : "0",
519
- margin: index === focusedIndex ? "0.125rem" : "0"
520
- });
521
- const arrowStyle = {
915
+ top: 0,
916
+ width: "1300px",
917
+ height: "280px",
918
+ minWidth: "1300px",
919
+ minHeight: "280px",
920
+ maxWidth: "none",
921
+ maxHeight: "none",
922
+ objectFit: "none",
923
+ transform: "none",
924
+ border: "none"
925
+ }
926
+ });
927
+ var ScrollableSVGWrapper = styled("div")({
928
+ position: "relative",
929
+ width: "100%",
930
+ height: "100%",
931
+ overflow: "auto",
932
+ transform: "rotateX(180deg)",
933
+ padding: "250px",
934
+ paddingTop: "200px",
935
+ boxSizing: "border-box",
936
+ "& > *": {
937
+ display: "block",
938
+ transform: "rotateX(180deg)"
939
+ }
940
+ });
941
+
942
+ // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.overlays.tsx
943
+ import { useEffect as useEffect2, useState as useState5 } from "react";
944
+ import { Box as Box2 } from "@mui/material";
945
+ import styled2 from "@emotion/styled";
946
+ import { jsx as jsx8 } from "react/jsx-runtime";
947
+ var baseOverlayStyles = {
948
+ tank: {
949
+ background: "linear-gradient(135deg, rgba(69, 90, 120, 0.95) 0%, rgba(52, 73, 94, 0.95) 100%)",
950
+ border: "1px solid #455a64",
951
+ color: "white",
952
+ borderRadius: "4px",
953
+ fontSize: "12px",
954
+ fontWeight: "500",
955
+ textAlign: "center",
956
+ padding: "8px",
957
+ minWidth: "60px",
958
+ boxShadow: "0 1px 4px rgba(69, 90, 120, 0.2)"
959
+ },
960
+ pressure: {
961
+ background: "linear-gradient(135deg, rgba(30, 50, 90, 0.75) 0%, rgba(20, 35, 70, 0.75) 100%)",
962
+ border: "1px solid #1e3a8a",
963
+ color: "white",
964
+ borderRadius: "4px",
965
+ fontSize: "11px",
966
+ fontWeight: "500",
967
+ textAlign: "center",
968
+ padding: "6px",
969
+ minWidth: "50px",
970
+ boxShadow: "0 1px 4px rgba(30, 50, 90, 0.15)"
971
+ },
972
+ vessel: {
973
+ background: "linear-gradient(135deg, rgba(84, 110, 122, 0.95) 0%, rgba(69, 90, 100, 0.95) 100%)",
974
+ border: "1px solid #546e7a",
975
+ color: "white",
976
+ borderRadius: "4px",
977
+ fontSize: "13px",
978
+ fontWeight: "500",
979
+ textAlign: "center",
980
+ padding: "8px",
981
+ minWidth: "70px",
982
+ boxShadow: "0 1px 4px rgba(84, 110, 122, 0.2)"
983
+ },
984
+ flow: {
985
+ background: "linear-gradient(135deg, rgba(102, 125, 102, 0.95) 0%, rgba(85, 107, 85, 0.95) 100%)",
986
+ border: "1px solid #689f38",
987
+ color: "white",
988
+ borderRadius: "4px",
989
+ fontSize: "11px",
990
+ fontWeight: "500",
991
+ textAlign: "center",
992
+ padding: "5px",
993
+ minWidth: "60px",
994
+ boxShadow: "0 1px 4px rgba(102, 125, 102, 0.2)"
995
+ }
996
+ };
997
+ var overlayStyles = baseOverlayStyles;
998
+ var SVGLockedOverlay = ({
999
+ svgX,
1000
+ svgY,
1001
+ children,
1002
+ theme,
1003
+ style = {},
1004
+ className = "",
1005
+ onClick,
1006
+ containerSelector = ".tff-svg-container"
1007
+ }) => {
1008
+ const [svgOffset, setSvgOffset] = useState5({ x: 0, y: 0 });
1009
+ useEffect2(() => {
1010
+ const updateSvgOffset = () => {
1011
+ const containerElement = document.querySelector(containerSelector);
1012
+ const svgElement = containerElement?.querySelector("svg");
1013
+ if (svgElement && containerElement) {
1014
+ const svgRect = svgElement.getBoundingClientRect();
1015
+ const containerRect = containerElement.getBoundingClientRect();
1016
+ setSvgOffset({
1017
+ x: svgRect.left - containerRect.left,
1018
+ y: svgRect.top - containerRect.top
1019
+ });
1020
+ }
1021
+ };
1022
+ updateSvgOffset();
1023
+ window.addEventListener("resize", updateSvgOffset);
1024
+ return () => window.removeEventListener("resize", updateSvgOffset);
1025
+ }, [containerSelector]);
1026
+ const pixelX = svgX + svgOffset.x;
1027
+ const pixelY = svgY + svgOffset.y;
1028
+ const appliedStyle = theme ? { ...overlayStyles[theme], ...style } : style;
1029
+ return /* @__PURE__ */ jsx8(
1030
+ Box2,
1031
+ {
1032
+ className: `svg-locked-overlay ${className}`.trim(),
1033
+ onClick,
1034
+ sx: {
1035
+ position: "absolute",
1036
+ left: `${pixelX}px`,
1037
+ top: `${pixelY}px`,
1038
+ cursor: onClick ? "pointer" : "default",
1039
+ zIndex: 10,
1040
+ pointerEvents: "auto",
1041
+ ...appliedStyle
1042
+ },
1043
+ children
1044
+ }
1045
+ );
1046
+ };
1047
+ var DashboardCard = styled2(Box2)(
1048
+ ({ svgX, svgY, cardType }) => ({
522
1049
  position: "absolute",
523
- right: "1rem",
524
- top: "50%",
525
- transform: `translateY(-50%) rotate(${isOpen ? "180deg" : "0deg"})`,
526
- transition: "transform 200ms ease",
527
- width: sizeStyles.iconSize,
528
- height: sizeStyles.iconSize,
529
- color: disabled ? "#6b7280" : "#94a3b8"
530
- };
531
- return /* @__PURE__ */ jsxs3(
1050
+ left: svgX,
1051
+ top: svgY,
1052
+ background: cardType === "critical" ? "linear-gradient(135deg, rgba(220, 53, 69, 0.95) 0%, rgba(173, 20, 87, 0.95) 100%)" : cardType === "warning" ? "linear-gradient(135deg, rgba(255, 193, 7, 0.95) 0%, rgba(255, 143, 0, 0.95) 100%)" : cardType === "vessel" ? "linear-gradient(135deg, rgba(46, 204, 113, 0.95) 0%, rgba(39, 174, 96, 0.95) 100%)" : cardType === "process" ? "linear-gradient(135deg, rgba(155, 89, 182, 0.95) 0%, rgba(142, 68, 173, 0.95) 100%)" : cardType === "control" ? "linear-gradient(135deg, rgba(26, 188, 156, 0.95) 0%, rgba(22, 160, 133, 0.95) 100%)" : "linear-gradient(135deg, rgba(52, 152, 219, 0.95) 0%, rgba(41, 128, 185, 0.95) 100%)",
1053
+ border: "1px solid rgba(255, 255, 255, 0.2)",
1054
+ borderRadius: "12px",
1055
+ padding: "12px",
1056
+ minWidth: "140px",
1057
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
1058
+ backdropFilter: "blur(10px)",
1059
+ color: "white",
1060
+ fontSize: "12px",
1061
+ fontWeight: "600",
1062
+ textAlign: "center",
1063
+ transform: "translate(-50%, -50%)",
1064
+ transition: "all 0.3s ease-in-out",
1065
+ cursor: "pointer",
1066
+ "&:hover": {
1067
+ transform: "translate(-50%, -50%) scale(1.05)",
1068
+ boxShadow: "0 12px 40px rgba(0, 0, 0, 0.4)"
1069
+ }
1070
+ })
1071
+ );
1072
+ var CardHeader = styled2("div")(({ color }) => ({
1073
+ fontSize: "10px",
1074
+ opacity: 0.9,
1075
+ marginBottom: "4px",
1076
+ textTransform: "uppercase",
1077
+ letterSpacing: "0.5px",
1078
+ color: color || "inherit",
1079
+ borderBottom: color ? `1px solid ${color}40` : "none",
1080
+ paddingBottom: color ? "2px" : "0"
1081
+ }));
1082
+ var CardValue = styled2("div")({
1083
+ fontSize: "16px",
1084
+ fontWeight: "700",
1085
+ marginBottom: "2px"
1086
+ });
1087
+ var CardUnit = styled2("div")({
1088
+ fontSize: "9px",
1089
+ opacity: 0.8
1090
+ });
1091
+
1092
+ // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.visuals.tsx
1093
+ import { Fragment, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1094
+ var TrendLine = ({
1095
+ values,
1096
+ width = 60,
1097
+ height = 20,
1098
+ color = "#4fc3f7",
1099
+ strokeWidth = 1.5,
1100
+ backgroundColor = "rgba(255, 255, 255, 0.1)"
1101
+ }) => {
1102
+ if (!values || values.length < 2) {
1103
+ return /* @__PURE__ */ jsx9(
1104
+ "div",
1105
+ {
1106
+ style: {
1107
+ width,
1108
+ height,
1109
+ backgroundColor,
1110
+ borderRadius: "2px",
1111
+ display: "flex",
1112
+ alignItems: "center",
1113
+ justifyContent: "center"
1114
+ },
1115
+ children: /* @__PURE__ */ jsx9("span", { style: { fontSize: "8px", color: "#ccc" }, children: "No data" })
1116
+ }
1117
+ );
1118
+ }
1119
+ const minValue = Math.min(...values);
1120
+ const maxValue = Math.max(...values);
1121
+ const range = maxValue - minValue;
1122
+ const normalizedValues = range === 0 ? values.map(() => height / 2) : values.map((v) => height - 4 - (v - minValue) / range * (height - 8));
1123
+ const pathData = normalizedValues.map((y, i) => {
1124
+ const x = 4 + i / (values.length - 1) * (width - 8);
1125
+ return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
1126
+ }).join(" ");
1127
+ return /* @__PURE__ */ jsx9(
532
1128
  "div",
533
1129
  {
534
- className: "selector-container",
535
- style: { display: "flex", flexDirection: "column", gap: "0.5rem" },
1130
+ style: {
1131
+ width,
1132
+ height,
1133
+ backgroundColor,
1134
+ borderRadius: "2px",
1135
+ position: "relative",
1136
+ overflow: "hidden"
1137
+ },
1138
+ children: /* @__PURE__ */ jsxs6("svg", { width, height, style: { position: "absolute", top: 0, left: 0 }, children: [
1139
+ /* @__PURE__ */ jsx9(
1140
+ "path",
1141
+ {
1142
+ d: pathData,
1143
+ fill: "none",
1144
+ stroke: color,
1145
+ strokeWidth,
1146
+ strokeLinecap: "round",
1147
+ strokeLinejoin: "round"
1148
+ }
1149
+ ),
1150
+ /* @__PURE__ */ jsx9(
1151
+ "circle",
1152
+ {
1153
+ cx: 4 + (width - 8),
1154
+ cy: normalizedValues[normalizedValues.length - 1],
1155
+ r: 1.5,
1156
+ fill: color
1157
+ }
1158
+ )
1159
+ ] })
1160
+ }
1161
+ );
1162
+ };
1163
+ var hexToRgba = (hex, alpha) => {
1164
+ const r = parseInt(hex.slice(1, 3), 16);
1165
+ const g = parseInt(hex.slice(3, 5), 16);
1166
+ const b = parseInt(hex.slice(5, 7), 16);
1167
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1168
+ };
1169
+ var CircularGauge = ({
1170
+ value,
1171
+ max,
1172
+ unit,
1173
+ label,
1174
+ size = 60,
1175
+ color = "rgba(255, 255, 255, 0.9)"
1176
+ }) => {
1177
+ const percentage = Math.min(value / max * 100, 100);
1178
+ const strokeWidth = 4;
1179
+ const radius = (size - strokeWidth) / 2;
1180
+ const circumference = radius * 2 * Math.PI;
1181
+ const strokeDasharray = circumference;
1182
+ const strokeDashoffset = circumference - percentage / 100 * circumference;
1183
+ const isHex = color.startsWith("#");
1184
+ const innerCircleColor = isHex ? hexToRgba(color, 0.8) : color;
1185
+ const borderColor = isHex ? hexToRgba(color, 0.9) : color;
1186
+ const backgroundColor = isHex ? hexToRgba(color, 0.15) : color;
1187
+ const borderStrokeColor = isHex ? hexToRgba(color, 0.4) : color;
1188
+ return /* @__PURE__ */ jsxs6(
1189
+ "div",
1190
+ {
1191
+ style: {
1192
+ display: "flex",
1193
+ flexDirection: "column",
1194
+ alignItems: "center"
1195
+ },
536
1196
  children: [
537
- label && /* @__PURE__ */ jsx4(
538
- "label",
1197
+ /* @__PURE__ */ jsxs6(
1198
+ "div",
539
1199
  {
540
- className: "selector-label",
541
1200
  style: {
542
- fontSize: "0.875rem",
543
- fontWeight: "600",
544
- color: "var(--selector-label-color, #cbd5e1)",
545
- textTransform: "uppercase",
546
- letterSpacing: "0.05em"
1201
+ position: "relative",
1202
+ width: size,
1203
+ height: size,
1204
+ borderRadius: "50%",
1205
+ backgroundColor,
1206
+ border: `1px solid ${borderStrokeColor}`
547
1207
  },
548
- children: label
549
- }
550
- ),
551
- /* @__PURE__ */ jsxs3("div", { ref: selectorRef, style: { position: "relative" }, children: [
552
- /* @__PURE__ */ jsxs3(
553
- "div",
554
- {
555
- className: `selector ${className}`,
556
- style: selectorStyle,
557
- onClick: () => !disabled && setIsOpen(!isOpen),
558
- onKeyDown: handleKeyDown,
559
- tabIndex: disabled ? -1 : 0,
560
- role: "combobox",
561
- "aria-expanded": isOpen,
562
- "aria-haspopup": "listbox",
563
- ...props,
564
- children: [
565
- /* @__PURE__ */ jsx4(
566
- "span",
1208
+ children: [
1209
+ /* @__PURE__ */ jsxs6("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
1210
+ /* @__PURE__ */ jsx9(
1211
+ "circle",
567
1212
  {
568
- style: {
569
- color: selectedOption ? statusStyles.color : "var(--selector-placeholder-text, #9ca3af)",
570
- overflow: "hidden",
571
- textOverflow: "ellipsis",
572
- whiteSpace: "nowrap"
573
- },
574
- children: selectedOption ? selectedOption.label : placeholder
1213
+ cx: size / 2,
1214
+ cy: size / 2,
1215
+ r: radius - strokeWidth - 2,
1216
+ fill: innerCircleColor,
1217
+ stroke: borderColor,
1218
+ strokeWidth: 1
575
1219
  }
576
1220
  ),
577
- /* @__PURE__ */ jsx4("svg", { style: arrowStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })
578
- ]
579
- }
580
- ),
581
- isOpen && /* @__PURE__ */ jsx4("div", { ref: dropdownRef, style: dropdownStyle, role: "listbox", children: options.map((option, index) => /* @__PURE__ */ jsxs3(
582
- "div",
583
- {
584
- style: optionStyle(option, index),
585
- onClick: () => {
586
- if (!option.disabled) {
587
- onChange(option.value);
588
- setIsOpen(false);
589
- setFocusedIndex(-1);
590
- }
591
- },
592
- onMouseEnter: () => !option.disabled && setFocusedIndex(index),
593
- role: "option",
594
- "aria-selected": option.value === value,
595
- "aria-disabled": option.disabled,
596
- title: option.description,
597
- children: [
598
- /* @__PURE__ */ jsxs3(
599
- "div",
1221
+ /* @__PURE__ */ jsx9(
1222
+ "circle",
600
1223
  {
601
- style: { display: "flex", justifyContent: "space-between", alignItems: "center" },
602
- children: [
603
- /* @__PURE__ */ jsx4("span", { children: option.label }),
604
- option.value === value && /* @__PURE__ */ jsx4(
605
- "svg",
606
- {
607
- style: { width: "1rem", height: "1rem", color: "#3b82f6" },
608
- fill: "currentColor",
609
- viewBox: "0 0 20 20",
610
- children: /* @__PURE__ */ jsx4(
611
- "path",
612
- {
613
- fillRule: "evenodd",
614
- d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",
615
- clipRule: "evenodd"
616
- }
617
- )
618
- }
619
- )
620
- ]
1224
+ cx: size / 2,
1225
+ cy: size / 2,
1226
+ r: radius,
1227
+ stroke: "rgba(255, 255, 255, 0.2)",
1228
+ strokeWidth,
1229
+ fill: "transparent"
621
1230
  }
622
1231
  ),
623
- option.description && /* @__PURE__ */ jsx4(
624
- "div",
1232
+ /* @__PURE__ */ jsx9(
1233
+ "circle",
625
1234
  {
626
- style: {
627
- fontSize: "0.75rem",
628
- color: "#9ca3af",
629
- marginTop: "0.25rem",
630
- fontWeight: "normal"
631
- },
632
- children: option.description
1235
+ cx: size / 2,
1236
+ cy: size / 2,
1237
+ r: radius,
1238
+ stroke: color,
1239
+ strokeWidth,
1240
+ fill: "transparent",
1241
+ strokeDasharray,
1242
+ strokeDashoffset,
1243
+ strokeLinecap: "round",
1244
+ style: { transition: "stroke-dashoffset 0.5s ease-in-out" }
633
1245
  }
634
1246
  )
635
- ]
1247
+ ] }),
1248
+ /* @__PURE__ */ jsxs6(
1249
+ "div",
1250
+ {
1251
+ style: {
1252
+ position: "absolute",
1253
+ top: "50%",
1254
+ left: "50%",
1255
+ transform: "translate(-50%, -50%)",
1256
+ fontSize: "11px",
1257
+ fontWeight: "700",
1258
+ color: "white",
1259
+ textAlign: "center",
1260
+ textShadow: "0 1px 2px rgba(0, 0, 0, 0.5)"
1261
+ },
1262
+ children: [
1263
+ value.toFixed(1),
1264
+ /* @__PURE__ */ jsx9("div", { style: { fontSize: "8px", opacity: 0.9, color: "white" }, children: unit })
1265
+ ]
1266
+ }
1267
+ )
1268
+ ]
1269
+ }
1270
+ ),
1271
+ /* @__PURE__ */ jsx9(
1272
+ "div",
1273
+ {
1274
+ style: {
1275
+ fontSize: "9px",
1276
+ marginTop: "4px",
1277
+ opacity: 0.9,
1278
+ color: "white"
636
1279
  },
637
- option.value
638
- )) })
639
- ] })
1280
+ children: label
1281
+ }
1282
+ )
640
1283
  ]
641
1284
  }
642
1285
  );
643
1286
  };
1287
+ var Sparkline = ({ data, width, height, color }) => {
1288
+ if (!data || data.length < 2) {
1289
+ return null;
1290
+ }
1291
+ const max = Math.max(...data);
1292
+ const min = Math.min(...data);
1293
+ const range = max - min;
1294
+ if (range === 0) {
1295
+ return null;
1296
+ }
1297
+ const points = data.map((value, index) => {
1298
+ const x = index / (data.length - 1) * (width - 8);
1299
+ const y = height - 8 - (value - min) / range * (height - 8);
1300
+ return `${x + 4},${y + 4}`;
1301
+ }).join(" ");
1302
+ return /* @__PURE__ */ jsx9(
1303
+ "div",
1304
+ {
1305
+ style: {
1306
+ width,
1307
+ height,
1308
+ backgroundColor: "rgba(0, 0, 0, 0.7)",
1309
+ border: "1px solid rgba(255, 255, 255, 0.3)",
1310
+ borderRadius: "4px",
1311
+ display: "flex",
1312
+ alignItems: "center",
1313
+ justifyContent: "center",
1314
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)"
1315
+ },
1316
+ children: /* @__PURE__ */ jsx9("svg", { width, height, children: /* @__PURE__ */ jsx9("polyline", { points, fill: "none", stroke: color, strokeWidth: "1.5", opacity: "0.9" }) })
1317
+ }
1318
+ );
1319
+ };
1320
+ var EquipmentIndicatorWithSparkline = ({
1321
+ svgX,
1322
+ svgY,
1323
+ color,
1324
+ label,
1325
+ size = 30,
1326
+ sparklineData,
1327
+ sparklineOffset = { x: 40, y: 0 }
1328
+ }) => {
1329
+ const hasSparkline = sparklineData && sparklineData.length > 1 && Math.max(...sparklineData) !== Math.min(...sparklineData);
1330
+ return /* @__PURE__ */ jsxs6(Fragment, { children: [
1331
+ /* @__PURE__ */ jsx9(SVGLockedOverlay, { svgX, svgY, style: { transform: "translate(-50%, -50%)" }, children: /* @__PURE__ */ jsx9(
1332
+ "div",
1333
+ {
1334
+ style: {
1335
+ width: size,
1336
+ height: size,
1337
+ borderRadius: "50%",
1338
+ backgroundColor: color,
1339
+ display: "flex",
1340
+ alignItems: "center",
1341
+ justifyContent: "center",
1342
+ color: "white",
1343
+ fontSize: "7px",
1344
+ fontWeight: "700",
1345
+ textAlign: "center",
1346
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
1347
+ border: "2px solid rgba(255, 255, 255, 0.3)"
1348
+ },
1349
+ children: label
1350
+ }
1351
+ ) }),
1352
+ hasSparkline && /* @__PURE__ */ jsx9(SVGLockedOverlay, { svgX: svgX + sparklineOffset.x, svgY: svgY + sparklineOffset.y, children: /* @__PURE__ */ jsx9(Sparkline, { data: sparklineData, width: 60, height: 20, color }) })
1353
+ ] });
1354
+ };
1355
+ var EquipmentIndicator = ({
1356
+ svgX,
1357
+ svgY,
1358
+ color,
1359
+ label,
1360
+ size = 30,
1361
+ style,
1362
+ className
1363
+ }) => {
1364
+ const overlayStyle = style !== void 0 ? { transform: "translate(-50%, -50%)", ...style } : { transform: "translate(-50%, -50%)" };
1365
+ return /* @__PURE__ */ jsx9(SVGLockedOverlay, { svgX, svgY, style: overlayStyle, className, children: /* @__PURE__ */ jsx9(
1366
+ "div",
1367
+ {
1368
+ style: {
1369
+ width: size,
1370
+ height: size,
1371
+ borderRadius: "50%",
1372
+ backgroundColor: color,
1373
+ display: "flex",
1374
+ alignItems: "center",
1375
+ justifyContent: "center",
1376
+ color: "white",
1377
+ fontSize: "7px",
1378
+ fontWeight: "700",
1379
+ textAlign: "center",
1380
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.3)",
1381
+ border: "2px solid rgba(255, 255, 255, 0.3)"
1382
+ },
1383
+ children: label
1384
+ }
1385
+ ) });
1386
+ };
644
1387
 
645
- // src/ControlPanel.tsx
646
- import { useState as useState3 } from "react";
647
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
648
- var ControlPanel = ({
649
- title,
650
- selectedMode,
651
- modeOptions,
652
- onModeChange,
653
- setpointValue,
654
- onSetpointChange,
655
- secondSetpointValue,
656
- onSecondSetpointChange,
657
- modeConfigs = {},
658
- defaultUnit = "Units",
659
- defaultLabel = "Setpoint",
660
- defaultMin = 0,
661
- defaultMax = 100,
662
- defaultPrecision = 2,
663
- status = "normal",
664
- collapsed = false,
665
- onCollapseChange,
666
- size = "medium",
667
- setpointReadOnly = false,
668
- showStartStopButtons = false,
669
- isRunning = false,
670
- onStart,
671
- onStop,
672
- startButtonText = "Start",
673
- stopButtonText = "Stop",
1388
+ // src/components/layout/FullscreenWorkspace/FullscreenWorkspace.tsx
1389
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1390
+ function FullscreenWorkspace({
1391
+ svgContent,
1392
+ overlays,
1393
+ leftPanel,
1394
+ rightPanel,
1395
+ footer,
1396
+ tabs,
1397
+ activeTab,
1398
+ onTabChange,
1399
+ defaultLeftCollapsed = false,
1400
+ defaultRightCollapsed = false,
1401
+ leftCollapsed: leftCollapsedProp,
1402
+ rightCollapsed: rightCollapsedProp,
1403
+ onLeftCollapseChange,
1404
+ onRightCollapseChange,
1405
+ displayMode,
1406
+ defaultDisplayMode = "standard",
1407
+ onDisplayModeChange,
1408
+ showDisplayModeToggle = true,
1409
+ showZoomControls = false,
1410
+ onZoomIn,
1411
+ onZoomOut,
1412
+ onResetZoom,
1413
+ zoomDisabled = false,
674
1414
  className = "",
675
- ...props
676
- }) => {
677
- const [isExpanded, setIsExpanded] = useState3(!collapsed);
678
- const toggleExpanded = () => {
679
- const newExpanded = !isExpanded;
680
- setIsExpanded(newExpanded);
681
- onCollapseChange?.(!newExpanded);
682
- };
683
- const currentModeConfig = modeConfigs[selectedMode] || {};
684
- const currentUnit = currentModeConfig.unit || defaultUnit;
685
- const currentLabel = currentModeConfig.label || defaultLabel;
686
- const currentMin = currentModeConfig.min ?? defaultMin;
687
- const currentMax = currentModeConfig.max ?? defaultMax;
688
- const currentPrecision = currentModeConfig.precision ?? defaultPrecision;
689
- const hasSecondSetpoint = currentModeConfig.hasSecondSetpoint || false;
690
- const getSizeStyles = () => {
691
- switch (size) {
692
- case "small":
693
- return {
694
- padding: "1rem",
695
- titleSize: "1rem",
696
- gap: "0.75rem"
697
- };
698
- case "large":
699
- return {
700
- padding: "1.5rem",
701
- titleSize: "1.5rem",
702
- gap: "1.25rem"
703
- };
704
- default:
705
- return {
706
- padding: "1.25rem",
707
- titleSize: "1.125rem",
708
- gap: "1rem"
709
- };
1415
+ ...rest
1416
+ }) {
1417
+ const [leftCollapsedState, setLeftCollapsedState] = React5.useState(defaultLeftCollapsed);
1418
+ const [rightCollapsedState, setRightCollapsedState] = React5.useState(defaultRightCollapsed);
1419
+ const [internalDisplayMode, setInternalDisplayMode] = React5.useState(displayMode ?? defaultDisplayMode);
1420
+ const [internalTab, setInternalTab] = React5.useState(
1421
+ tabs && tabs.length > 0 ? tabs[0].value : ""
1422
+ );
1423
+ const leftCollapsed = leftCollapsedProp ?? leftCollapsedState;
1424
+ const rightCollapsed = rightCollapsedProp ?? rightCollapsedState;
1425
+ const isDisplayModeControlled = displayMode !== void 0;
1426
+ const currentDisplayMode = isDisplayModeControlled ? displayMode : internalDisplayMode;
1427
+ const isTabControlled = activeTab !== void 0 && activeTab !== null;
1428
+ const resolvedTabs = React5.useMemo(() => tabs ?? [], [tabs]);
1429
+ const defaultTabValue = resolvedTabs.length > 0 ? resolvedTabs[0].value : "";
1430
+ const currentTab = isTabControlled ? activeTab : internalTab || defaultTabValue;
1431
+ React5.useEffect(() => {
1432
+ if (resolvedTabs.length > 0) {
1433
+ const values = resolvedTabs.map((tab) => tab.value);
1434
+ if (!values.includes(currentTab)) {
1435
+ const fallback = activeTab ?? defaultTabValue;
1436
+ setInternalTab(fallback);
1437
+ }
710
1438
  }
711
- };
712
- const getStatusStyles = () => {
713
- switch (status) {
714
- case "alarm":
715
- return {
716
- borderColor: "var(--control-panel-alarm-border, #ef4444)",
717
- backgroundColor: "var(--control-panel-alarm-bg, rgba(239, 68, 68, 0.1))",
718
- titleColor: "var(--control-panel-alarm-text, #fecaca)",
719
- boxShadow: "var(--control-panel-alarm-shadow, 0 0 20px rgba(239, 68, 68, 0.3))",
720
- glowEffect: "var(--control-panel-alarm-glow, 0 0 30px rgba(239, 68, 68, 0.2))"
721
- };
722
- case "warning":
723
- return {
724
- borderColor: "var(--control-panel-warning-border, #f59e0b)",
725
- backgroundColor: "var(--control-panel-warning-bg, rgba(245, 158, 11, 0.1))",
726
- titleColor: "var(--control-panel-warning-text, #fed7aa)",
727
- boxShadow: "var(--control-panel-warning-shadow, 0 0 20px rgba(245, 158, 11, 0.3))",
728
- glowEffect: "var(--control-panel-warning-glow, 0 0 30px rgba(245, 158, 11, 0.2))"
729
- };
730
- case "disabled":
731
- return {
732
- borderColor: "var(--control-panel-disabled-border, #6b7280)",
733
- backgroundColor: "var(--control-panel-disabled-bg, rgba(107, 114, 128, 0.1))",
734
- titleColor: "var(--control-panel-disabled-text, #9ca3af)",
735
- boxShadow: "var(--control-panel-disabled-shadow, 0 8px 32px rgba(0, 0, 0, 0.2))",
736
- glowEffect: "var(--control-panel-disabled-glow, none)"
737
- };
738
- default:
739
- return {
740
- borderColor: "var(--control-panel-normal-border, #64748b)",
741
- backgroundColor: "var(--control-panel-normal-bg, rgba(30, 41, 59, 0.9))",
742
- titleColor: "var(--control-panel-normal-text, #f1f5f9)",
743
- boxShadow: "var(--control-panel-normal-shadow, 0 8px 32px rgba(0, 0, 0, 0.3))",
744
- glowEffect: "var(--control-panel-normal-glow, 0 0 20px rgba(59, 130, 246, 0.1))"
745
- };
1439
+ }, [resolvedTabs, activeTab, currentTab, defaultTabValue]);
1440
+ React5.useEffect(() => {
1441
+ if (displayMode !== void 0) {
1442
+ setInternalDisplayMode(displayMode);
746
1443
  }
1444
+ }, [displayMode]);
1445
+ const handleLeftToggle = () => {
1446
+ const next = !leftCollapsed;
1447
+ if (leftCollapsedProp === void 0) {
1448
+ setLeftCollapsedState(next);
1449
+ }
1450
+ onLeftCollapseChange?.(next);
747
1451
  };
748
- const sizeStyles = getSizeStyles();
749
- const statusStyles = getStatusStyles();
750
- const panelStyle = {
751
- border: "2px solid",
752
- borderRadius: "1.5rem",
753
- backdropFilter: "blur(12px)",
754
- transition: "all 400ms cubic-bezier(0.4, 0, 0.2, 1)",
755
- position: "relative",
756
- overflow: "hidden",
757
- minWidth: "300px",
758
- maxWidth: "400px",
759
- minHeight: "400px",
760
- width: "100%",
761
- ...statusStyles,
762
- boxShadow: isExpanded ? statusStyles.glowEffect : statusStyles.boxShadow
763
- };
764
- const headerStyle = {
765
- display: "flex",
766
- alignItems: "center",
767
- justifyContent: "space-between",
768
- padding: sizeStyles.padding,
769
- cursor: onCollapseChange ? "pointer" : "default",
770
- borderBottom: isExpanded ? `1px solid ${statusStyles.borderColor}40` : "none",
771
- transition: "all 300ms ease"
772
- };
773
- const titleStyle = {
774
- fontSize: sizeStyles.titleSize,
775
- fontWeight: "bold",
776
- color: statusStyles.titleColor,
777
- fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
778
- letterSpacing: "0.05em",
779
- margin: 0
780
- };
781
- const contentStyle = {
782
- padding: isExpanded ? sizeStyles.padding : "0",
783
- paddingTop: isExpanded ? sizeStyles.padding : "0",
784
- maxHeight: isExpanded ? "500px" : "0",
785
- opacity: isExpanded ? 1 : 0,
786
- overflow: "hidden",
787
- transition: "all 400ms cubic-bezier(0.4, 0, 0.2, 1)",
788
- display: "flex",
789
- flexDirection: "column",
790
- gap: sizeStyles.gap,
791
- justifyContent: "space-between",
792
- flex: 1
1452
+ const handleRightToggle = () => {
1453
+ const next = !rightCollapsed;
1454
+ if (rightCollapsedProp === void 0) {
1455
+ setRightCollapsedState(next);
1456
+ }
1457
+ onRightCollapseChange?.(next);
793
1458
  };
794
- const chevronStyle = {
795
- width: "1.5rem",
796
- height: "1.5rem",
797
- color: statusStyles.titleColor,
798
- transform: `rotate(${isExpanded ? "180deg" : "0deg"})`,
799
- transition: "transform 300ms ease",
800
- opacity: onCollapseChange ? 1 : 0.3
1459
+ const handleModeToggle = () => {
1460
+ const nextMode = currentDisplayMode === "standard" ? "dashboard" : "standard";
1461
+ if (!isDisplayModeControlled) {
1462
+ setInternalDisplayMode(nextMode);
1463
+ }
1464
+ onDisplayModeChange?.(nextMode);
801
1465
  };
802
- const statusBadgeStyle = {
803
- position: "absolute",
804
- top: "0.5rem",
805
- right: "0.5rem",
806
- width: "0.75rem",
807
- height: "0.75rem",
808
- borderRadius: "50%",
809
- backgroundColor: status === "alarm" ? "#ef4444" : status === "warning" ? "#f59e0b" : status === "disabled" ? "#6b7280" : "#22c55e",
810
- boxShadow: `0 0 10px ${status === "alarm" ? "#ef4444" : status === "warning" ? "#f59e0b" : status === "disabled" ? "#6b7280" : "#22c55e"}80`,
811
- animation: status === "alarm" ? "pulse 2s infinite" : "none"
1466
+ const handleTabChangeInternal = (_, value) => {
1467
+ if (!isTabControlled) {
1468
+ setInternalTab(value);
1469
+ }
1470
+ onTabChange?.(value);
812
1471
  };
813
- return /* @__PURE__ */ jsxs4("div", { style: panelStyle, className: `control-panel glass-card ${className}`, ...props, children: [
814
- /* @__PURE__ */ jsx5("div", { style: statusBadgeStyle }),
815
- /* @__PURE__ */ jsxs4("div", { style: headerStyle, onClick: onCollapseChange ? toggleExpanded : void 0, children: [
816
- /* @__PURE__ */ jsx5("h3", { style: titleStyle, children: title }),
817
- onCollapseChange && /* @__PURE__ */ jsx5("svg", { style: chevronStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })
818
- ] }),
819
- /* @__PURE__ */ jsxs4("div", { style: contentStyle, children: [
820
- /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
821
- Selector,
822
- {
823
- value: selectedMode,
824
- onChange: onModeChange,
825
- options: modeOptions,
826
- label: "Control Mode",
827
- status,
828
- size: size === "small" ? "small" : size === "large" ? "large" : "medium",
829
- disabled: status === "disabled"
830
- }
831
- ) }),
832
- /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
833
- ValueEntry,
834
- {
835
- value: setpointValue,
836
- onChange: onSetpointChange,
837
- label: currentLabel,
838
- unit: currentUnit,
839
- min: currentMin,
840
- max: currentMax,
841
- precision: currentPrecision,
842
- status,
843
- readOnly: setpointReadOnly || status === "disabled",
844
- disabled: status === "disabled"
845
- }
846
- ) }),
847
- hasSecondSetpoint && onSecondSetpointChange && /* @__PURE__ */ jsx5("div", { children: /* @__PURE__ */ jsx5(
848
- ValueEntry,
849
- {
850
- value: secondSetpointValue || 0,
851
- onChange: onSecondSetpointChange,
852
- label: currentModeConfig.secondLabel || "Time",
853
- unit: currentModeConfig.secondUnit || "sec",
854
- min: currentModeConfig.secondMin ?? 0,
855
- max: currentModeConfig.secondMax ?? 999,
856
- precision: currentModeConfig.secondPrecision ?? 0,
857
- status,
858
- readOnly: setpointReadOnly || status === "disabled",
859
- disabled: status === "disabled"
860
- }
861
- ) }),
862
- showStartStopButtons && (onStart || onStop) && /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "0.75rem" }, children: [
863
- onStart && /* @__PURE__ */ jsx5(
864
- Button,
1472
+ const currentTabContent = resolvedTabs.find((tab) => tab.value === currentTab)?.content ?? null;
1473
+ const showLeftToggle = Boolean(leftPanel);
1474
+ const showRightToggle = Boolean(rightPanel);
1475
+ return /* @__PURE__ */ jsxs7(
1476
+ FullscreenContainer,
1477
+ {
1478
+ leftCollapsed,
1479
+ rightCollapsed,
1480
+ className: `tff-svg-container ${className}`.trim(),
1481
+ ...rest,
1482
+ children: [
1483
+ showLeftToggle && /* @__PURE__ */ jsx10(
1484
+ LeftToggleButton,
865
1485
  {
866
- variant: "primary",
867
- onClick: onStart,
868
- disabled: isRunning || status === "disabled",
869
- style: {
870
- flex: 1,
871
- fontSize: "0.875rem",
872
- padding: "0.75rem 1rem",
873
- backgroundColor: isRunning ? "#6b7280" : void 0
874
- },
875
- children: startButtonText
1486
+ collapsed: leftCollapsed,
1487
+ onClick: handleLeftToggle,
1488
+ "aria-label": leftCollapsed ? "Expand left panel" : "Collapse left panel",
1489
+ children: leftCollapsed ? /* @__PURE__ */ jsx10(ChevronRight, {}) : /* @__PURE__ */ jsx10(ChevronLeft, {})
876
1490
  }
877
1491
  ),
878
- onStop && /* @__PURE__ */ jsx5(
879
- Button,
1492
+ showRightToggle && /* @__PURE__ */ jsx10(
1493
+ RightToggleButton,
880
1494
  {
881
- variant: "secondary",
882
- onClick: onStop,
883
- disabled: !isRunning || status === "disabled",
884
- style: {
885
- flex: 1,
886
- fontSize: "0.875rem",
887
- padding: "0.75rem 1rem",
888
- backgroundColor: !isRunning ? "#6b7280" : "#dc2626",
889
- borderColor: !isRunning ? "#6b7280" : "#dc2626"
890
- },
891
- children: stopButtonText
1495
+ collapsed: rightCollapsed,
1496
+ onClick: handleRightToggle,
1497
+ "aria-label": rightCollapsed ? "Expand right panel" : "Collapse right panel",
1498
+ children: rightCollapsed ? /* @__PURE__ */ jsx10(ChevronLeft, {}) : /* @__PURE__ */ jsx10(ChevronRight, {})
892
1499
  }
893
- )
894
- ] }),
895
- /* @__PURE__ */ jsxs4(
896
- "div",
897
- {
898
- className: "control-panel-info",
899
- style: {
900
- display: "flex",
901
- justifyContent: "space-between",
902
- alignItems: "center",
903
- padding: "0.5rem 0",
904
- fontSize: "0.75rem",
905
- color: "var(--control-panel-info-text, #9ca3af)",
906
- borderTop: `1px solid ${statusStyles.borderColor}20`,
907
- fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
908
- },
909
- children: [
910
- /* @__PURE__ */ jsxs4("span", { children: [
911
- "Mode: ",
912
- selectedMode.toUpperCase()
913
- ] }),
914
- /* @__PURE__ */ jsxs4("div", { style: { display: "flex", gap: "1rem" }, children: [
915
- /* @__PURE__ */ jsxs4("span", { children: [
916
- "SP: ",
917
- String(setpointValue),
918
- currentUnit
919
- ] }),
920
- hasSecondSetpoint && secondSetpointValue && /* @__PURE__ */ jsxs4("span", { children: [
921
- currentModeConfig.secondLabel,
922
- ": ",
923
- String(secondSetpointValue),
924
- currentModeConfig.secondUnit
925
- ] })
926
- ] })
927
- ]
928
- }
929
- )
930
- ] })
931
- ] });
932
- };
1500
+ ),
1501
+ showDisplayModeToggle && /* @__PURE__ */ jsx10(
1502
+ DisplayModeToggle,
1503
+ {
1504
+ mode: currentDisplayMode,
1505
+ onClick: handleModeToggle,
1506
+ "aria-label": "Toggle display mode",
1507
+ children: currentDisplayMode === "dashboard" ? "Dashboard Mode" : "Standard Mode"
1508
+ }
1509
+ ),
1510
+ /* @__PURE__ */ jsxs7(SVGArea, { children: [
1511
+ showZoomControls && /* @__PURE__ */ jsxs7(ZoomControls, { children: [
1512
+ /* @__PURE__ */ jsx10(
1513
+ ZoomButton,
1514
+ {
1515
+ onClick: onZoomIn,
1516
+ disabled: zoomDisabled || !onZoomIn,
1517
+ size: "small",
1518
+ "aria-label": "Zoom in",
1519
+ children: "+"
1520
+ }
1521
+ ),
1522
+ /* @__PURE__ */ jsx10(
1523
+ ZoomButton,
1524
+ {
1525
+ onClick: onZoomOut,
1526
+ disabled: zoomDisabled || !onZoomOut,
1527
+ size: "small",
1528
+ "aria-label": "Zoom out",
1529
+ children: "-"
1530
+ }
1531
+ ),
1532
+ onResetZoom && /* @__PURE__ */ jsx10(
1533
+ ZoomButton,
1534
+ {
1535
+ onClick: onResetZoom,
1536
+ disabled: zoomDisabled,
1537
+ size: "small",
1538
+ "aria-label": "Reset zoom",
1539
+ children: "Reset"
1540
+ }
1541
+ )
1542
+ ] }),
1543
+ /* @__PURE__ */ jsx10(ScrollableSVGWrapper, { className: "svg-scroll-wrapper", children: /* @__PURE__ */ jsxs7(FixedSVGContainer, { className: "svg-container", children: [
1544
+ svgContent,
1545
+ overlays
1546
+ ] }) })
1547
+ ] }),
1548
+ /* @__PURE__ */ jsx10(LeftPanel, { collapsed: leftCollapsed, children: resolvedTabs.length > 0 ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
1549
+ /* @__PURE__ */ jsx10(
1550
+ PanelTabs,
1551
+ {
1552
+ value: currentTab,
1553
+ onChange: handleTabChangeInternal,
1554
+ "aria-label": "Left panel tabs",
1555
+ children: resolvedTabs.map((tab) => /* @__PURE__ */ jsx10(Tab, { label: tab.label, value: tab.value }, tab.value))
1556
+ }
1557
+ ),
1558
+ /* @__PURE__ */ jsx10(PanelContent, { children: currentTabContent })
1559
+ ] }) : leftPanel }),
1560
+ /* @__PURE__ */ jsx10(RightPanel, { collapsed: rightCollapsed, children: rightPanel }),
1561
+ /* @__PURE__ */ jsx10(FooterPanel, { children: footer })
1562
+ ]
1563
+ }
1564
+ );
1565
+ }
933
1566
 
934
- // src/ThemeContext.tsx
935
- import { createContext, useContext, useState as useState4, useEffect as useEffect2 } from "react";
936
- import { jsx as jsx6 } from "react/jsx-runtime";
1567
+ // src/theme/ThemeContext.tsx
1568
+ import { createContext, useContext, useState as useState6, useEffect as useEffect3 } from "react";
1569
+ import { jsx as jsx11 } from "react/jsx-runtime";
937
1570
  var ThemeContext = createContext(void 0);
938
1571
  var useTheme = () => {
939
1572
  const context = useContext(ThemeContext);
@@ -943,8 +1576,8 @@ var useTheme = () => {
943
1576
  return context;
944
1577
  };
945
1578
  var ThemeProvider = ({ children }) => {
946
- const [theme, setThemeState] = useState4("modern");
947
- useEffect2(() => {
1579
+ const [theme, setThemeState] = useState6("modern");
1580
+ useEffect3(() => {
948
1581
  document.body.className = "";
949
1582
  document.body.classList.add(`theme-${theme}`);
950
1583
  document.documentElement.className = "";
@@ -956,11 +1589,11 @@ var ThemeProvider = ({ children }) => {
956
1589
  const setTheme = (newTheme) => {
957
1590
  setThemeState(newTheme);
958
1591
  };
959
- return /* @__PURE__ */ jsx6(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
1592
+ return /* @__PURE__ */ jsx11(ThemeContext.Provider, { value: { theme, toggleTheme, setTheme }, children });
960
1593
  };
961
1594
 
962
- // src/ThemeToggle.tsx
963
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1595
+ // src/theme/ThemeToggle.tsx
1596
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
964
1597
  var ThemeToggle = ({
965
1598
  size = "medium",
966
1599
  position = "top-right",
@@ -1031,20 +1664,47 @@ var ThemeToggle = ({
1031
1664
  transition: "all 0.2s ease",
1032
1665
  boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)"
1033
1666
  };
1034
- return /* @__PURE__ */ jsxs5("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
1035
- /* @__PURE__ */ jsx7("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
1036
- /* @__PURE__ */ jsx7("div", { style: toggleStyle, children: /* @__PURE__ */ jsx7("div", { style: thumbStyle }) }),
1037
- /* @__PURE__ */ jsx7("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
1667
+ return /* @__PURE__ */ jsxs8("div", { style: currentStyle, onClick: toggleTheme, className, ...props, children: [
1668
+ /* @__PURE__ */ jsx12("span", { children: theme === "modern" ? "\u{1F3A8} Modern" : "\u{1F3ED} HMI" }),
1669
+ /* @__PURE__ */ jsx12("div", { style: toggleStyle, children: /* @__PURE__ */ jsx12("div", { style: thumbStyle }) }),
1670
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: "0.75rem", opacity: 0.8 }, children: theme === "modern" ? "Industrial Design" : "High Performance" })
1038
1671
  ] });
1039
1672
  };
1040
1673
  export {
1041
1674
  Button,
1675
+ CardHeader,
1676
+ CardUnit,
1677
+ CardValue,
1678
+ CircularGauge,
1042
1679
  ControlPanel,
1680
+ DashboardCard,
1681
+ DisplayModeToggle,
1682
+ EquipmentIndicator,
1683
+ EquipmentIndicatorWithSparkline,
1684
+ FixedSVGContainer,
1685
+ FooterPanel,
1686
+ FullscreenContainer,
1687
+ FullscreenWorkspace,
1688
+ LeftPanel,
1689
+ LeftToggleButton,
1690
+ LegacyValueEntry,
1691
+ PanelContent,
1692
+ PanelTabs,
1693
+ RightPanel,
1694
+ RightToggleButton,
1695
+ SVGArea,
1696
+ SVGLockedOverlay,
1697
+ ScrollableSVGWrapper,
1043
1698
  Selector,
1699
+ Sparkline,
1044
1700
  StatusIndicator,
1045
1701
  ThemeProvider,
1046
1702
  ThemeToggle,
1703
+ TrendLine,
1047
1704
  ValueEntry,
1705
+ ZoomButton,
1706
+ ZoomControls,
1707
+ overlayStyles,
1048
1708
  useTheme
1049
1709
  };
1050
1710
  //# sourceMappingURL=index.js.map