@wandelbots/wandelbots-js-react-components 2.44.0 → 2.45.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.
Files changed (59) hide show
  1. package/dist/components/CycleTimer/DefaultVariant.d.ts.map +1 -1
  2. package/dist/components/CycleTimer/SmallVariant.d.ts.map +1 -1
  3. package/dist/components/CycleTimer/index.d.ts +4 -5
  4. package/dist/components/CycleTimer/index.d.ts.map +1 -1
  5. package/dist/components/CycleTimer/types.d.ts +2 -3
  6. package/dist/components/CycleTimer/types.d.ts.map +1 -1
  7. package/dist/components/CycleTimer/useTimerLogic.d.ts +1 -2
  8. package/dist/components/CycleTimer/useTimerLogic.d.ts.map +1 -1
  9. package/dist/components/ProgramControl.d.ts +7 -1
  10. package/dist/components/ProgramControl.d.ts.map +1 -1
  11. package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
  12. package/dist/components/Timer/Timer.d.ts +3 -0
  13. package/dist/components/Timer/Timer.d.ts.map +1 -0
  14. package/dist/components/Timer/TimerDefaultVariant.d.ts +10 -0
  15. package/dist/components/Timer/TimerDefaultVariant.d.ts.map +1 -0
  16. package/dist/components/Timer/TimerSmallVariant.d.ts +11 -0
  17. package/dist/components/Timer/TimerSmallVariant.d.ts.map +1 -0
  18. package/dist/components/Timer/index.d.ts +19 -0
  19. package/dist/components/Timer/index.d.ts.map +1 -0
  20. package/dist/components/Timer/types.d.ts +36 -0
  21. package/dist/components/Timer/types.d.ts.map +1 -0
  22. package/dist/components/Timer/useTimerAnimations.d.ts +11 -0
  23. package/dist/components/Timer/useTimerAnimations.d.ts.map +1 -0
  24. package/dist/components/Timer/useTimerLogic.d.ts +20 -0
  25. package/dist/components/Timer/useTimerLogic.d.ts.map +1 -0
  26. package/dist/components/Timer/utils.d.ts +9 -0
  27. package/dist/components/Timer/utils.d.ts.map +1 -0
  28. package/dist/components/jogging/PoseCartesianValues.d.ts +3 -5
  29. package/dist/components/jogging/PoseCartesianValues.d.ts.map +1 -1
  30. package/dist/components/jogging/PoseJointValues.d.ts +3 -5
  31. package/dist/components/jogging/PoseJointValues.d.ts.map +1 -1
  32. package/dist/index.cjs +48 -48
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6342 -5942
  37. package/dist/index.js.map +1 -1
  38. package/package.json +5 -5
  39. package/src/components/CycleTimer/DefaultVariant.tsx +0 -2
  40. package/src/components/CycleTimer/SmallVariant.tsx +2 -5
  41. package/src/components/CycleTimer/index.tsx +4 -5
  42. package/src/components/CycleTimer/types.ts +1 -3
  43. package/src/components/CycleTimer/useTimerLogic.ts +40 -96
  44. package/src/components/CycleTimer/utils.ts +3 -3
  45. package/src/components/ProgramControl.tsx +31 -3
  46. package/src/components/ProgramStateIndicator.tsx +31 -1
  47. package/src/components/Timer/Timer.ts +2 -0
  48. package/src/components/Timer/TimerDefaultVariant.tsx +140 -0
  49. package/src/components/Timer/TimerSmallVariant.tsx +140 -0
  50. package/src/components/Timer/index.tsx +101 -0
  51. package/src/components/Timer/types.ts +38 -0
  52. package/src/components/Timer/useTimerAnimations.ts +94 -0
  53. package/src/components/Timer/useTimerLogic.ts +214 -0
  54. package/src/components/Timer/utils.ts +15 -0
  55. package/src/components/jogging/PoseCartesianValues.tsx +16 -82
  56. package/src/components/jogging/PoseJointValues.tsx +16 -82
  57. package/src/i18n/locales/de/translations.json +7 -0
  58. package/src/i18n/locales/en/translations.json +7 -0
  59. package/src/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wandelbots/wandelbots-js-react-components",
3
- "version": "2.44.0",
3
+ "version": "2.45.0",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -59,8 +59,8 @@
59
59
  "@rollup/plugin-node-resolve": "^16.0.0",
60
60
  "@rollup/plugin-terser": "^0.4.4",
61
61
  "@rollup/plugin-typescript": "^12.1.2",
62
- "@storybook/addon-docs": "^9.1.3",
63
- "@storybook/react-vite": "^9.1.3",
62
+ "@storybook/addon-docs": "^9.1.6",
63
+ "@storybook/react-vite": "^9.1.6",
64
64
  "@storybook/test-runner": "^0.23.0",
65
65
  "@svgr/rollup": "^8.1.0",
66
66
  "@testing-library/jest-dom": "^6.6.3",
@@ -72,7 +72,7 @@
72
72
  "@vitejs/plugin-react": "^4.3.4",
73
73
  "@wandelbots/nova-js": "^2.1.0",
74
74
  "add": "^2.0.6",
75
- "eslint-plugin-storybook": "^9.1.3",
75
+ "eslint-plugin-storybook": "^9.1.6",
76
76
  "glob": "^11.0.1",
77
77
  "http-server": "^14.1.1",
78
78
  "husky": "^9.1.7",
@@ -95,7 +95,7 @@
95
95
  "rollup-plugin-peer-deps-external": "^2.2.4",
96
96
  "rollup-plugin-postcss": "^4.0.2",
97
97
  "semantic-release": "^24.2.3",
98
- "storybook": "^9.1.3",
98
+ "storybook": "^9.1.6",
99
99
  "storybook-preset-inline-svg": "^1.0.1",
100
100
  "three": "^0.174.0",
101
101
  "three-stdlib": "^2.35.14",
@@ -114,7 +114,6 @@ export const DefaultVariant = ({
114
114
  showLabels &&
115
115
  !hasError &&
116
116
  currentState !== "idle" &&
117
- currentState !== "countup" &&
118
117
  currentState !== "success"
119
118
  }
120
119
  timeout={300}
@@ -277,7 +276,6 @@ export const DefaultVariant = ({
277
276
  showLabels &&
278
277
  !hasError &&
279
278
  currentState !== "idle" &&
280
- currentState !== "countup" &&
281
279
  currentState !== "success"
282
280
  }
283
281
  timeout={300}
@@ -31,7 +31,7 @@ export const SmallVariant = ({
31
31
  } = animationState
32
32
 
33
33
  // Simple text-only mode for compact variant in certain states
34
- if (compact && (currentState === "countup" || currentState === "idle")) {
34
+ if (compact && currentState === "idle") {
35
35
  return (
36
36
  <Box
37
37
  className={className}
@@ -72,10 +72,7 @@ export const SmallVariant = ({
72
72
  }}
73
73
  >
74
74
  {/* Animated progress ring icon */}
75
- {!(
76
- currentState === "countup" ||
77
- (currentState === "idle" && compact)
78
- ) && (
75
+ {!(currentState === "idle" && compact) && (
79
76
  <Box
80
77
  sx={{
81
78
  width: 20,
@@ -8,19 +8,18 @@ import { useAnimations } from "./useAnimations"
8
8
  import { useTimerLogic } from "./useTimerLogic"
9
9
 
10
10
  /**
11
- * A circular gauge timer component that shows the remaining time of a cycle or counts up
11
+ * A circular gauge timer component for cycle-specific timing operations
12
12
  *
13
13
  * Features:
14
14
  * - Custom SVG circular gauge with 264px diameter and 40px thickness
15
- * - Multiple states: idle, measuring, measured, countdown, countup, success
15
+ * - Multiple states: idle, measuring, measured, countdown, success
16
16
  * - Idle state: shows "Waiting for program cycle" with transparent inner circle
17
17
  * - Measuring state: counts up with "Cycle Time" / "measuring..." labels
18
18
  * - Measured state: shows final time with "Cycle Time" / "determined" labels in pulsating green
19
19
  * - Countdown mode: shows remaining time prominently, counts down to zero
20
- * - Count-up mode: shows elapsed time without special labels
21
20
  * - Success state: brief green flash after cycle completion
22
21
  * - Displays appropriate labels based on state
23
- * - Automatically counts down/up and triggers callback when reaching zero (countdown only)
22
+ * - Automatically counts down and triggers callback when reaching zero
24
23
  * - Full timer control: start, pause, resume functionality
25
24
  * - Support for starting with elapsed time (resume mid-cycle)
26
25
  * - Error state support: pauses timer and shows error styling (red color)
@@ -28,7 +27,7 @@ import { useTimerLogic } from "./useTimerLogic"
28
27
  * - Pulsating text animation for completed measuring state
29
28
  * - Fully localized with i18next
30
29
  * - Material-UI theming integration
31
- * - Small variant with animated progress icon (gauge border only) next to text or simple text-only mode
30
+ * - Small variant with animated progress icon (gauge border only) next to text
32
31
  */
33
32
  export const CycleTimer = externalizeComponent(
34
33
  observer(
@@ -3,13 +3,11 @@ export type CycleTimerState =
3
3
  | "measuring" // Counting up without max time, showing "Cycle Time" / "measuring..."
4
4
  | "measured" // Completed measuring state showing "Cycle Time" / "determined" with pulsating green text
5
5
  | "countdown" // Counting down with max time
6
- | "countup" // Simple count up without special text
7
6
  | "success" // Brief success state after cycle completion
8
7
 
9
8
  export interface CycleTimerControls {
10
- startNewCycle: (maxTimeSeconds?: number, elapsedSeconds?: number) => void
9
+ startNewCycle: (maxTimeSeconds: number, elapsedSeconds?: number) => void
11
10
  startMeasuring: (elapsedSeconds?: number) => void
12
- startCountUp: (elapsedSeconds?: number) => void
13
11
  setIdle: () => void
14
12
  completeMeasuring: () => void
15
13
  pause: () => void
@@ -88,114 +88,66 @@ export const useTimerLogic = ({
88
88
  [autoStart, progressInterpolator],
89
89
  )
90
90
 
91
- const startCountUp = useCallback(
92
- (elapsedSeconds: number = 0) => {
93
- const initialProgress = ((elapsedSeconds / 60) % 1) * 100
94
- setTimerState((prev) => ({
95
- ...prev,
96
- currentState: "countup",
97
- maxTime: null,
98
- remainingTime: elapsedSeconds,
99
- isPausedState: false,
100
- currentProgress: initialProgress, // Immediately set progress
101
- }))
102
- pausedTimeRef.current = 0
103
-
104
- progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
105
-
106
- if (autoStart) {
107
- startTimeRef.current = Date.now() - elapsedSeconds * 1000
108
- setTimerState((prev) => ({ ...prev, isRunning: true }))
109
- } else {
110
- startTimeRef.current = null
111
- }
112
- },
113
- [autoStart, progressInterpolator],
114
- )
115
-
116
91
  const startNewCycle = useCallback(
117
- (maxTimeSeconds?: number, elapsedSeconds: number = 0) => {
92
+ (maxTimeSeconds: number, elapsedSeconds: number = 0) => {
118
93
  // Stop any running timer first to prevent conflicts
119
94
  setTimerState((prev) => ({ ...prev, isRunning: false }))
120
95
  startTimeRef.current = null
121
96
 
122
- const newState = maxTimeSeconds !== undefined ? "countdown" : "countup"
123
97
  setTimerState((prev) => ({
124
98
  ...prev,
125
- currentState: newState,
126
- maxTime: maxTimeSeconds ?? null,
99
+ currentState: "countdown",
100
+ maxTime: maxTimeSeconds,
127
101
  isPausedState: false,
128
102
  }))
129
103
  pausedTimeRef.current = 0
130
104
 
131
- if (maxTimeSeconds !== undefined) {
132
- // Count-down mode
133
- const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
134
- const initialProgress =
135
- elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
136
-
137
- setTimerState((prev) => ({
138
- ...prev,
139
- remainingTime: remainingSeconds,
140
- currentProgress: initialProgress, // Immediately set progress
141
- }))
142
-
143
- progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
144
-
145
- if (remainingSeconds === 0) {
146
- setTimerState((prev) => ({ ...prev, isRunning: false }))
147
- startTimeRef.current = null
148
- if (onCycleEnd) {
149
- queueMicrotask(() => onCycleEnd())
150
- }
151
- } else if (autoStart) {
152
- setTimeout(() => {
153
- startTimeRef.current = Date.now() - elapsedSeconds * 1000
154
- setTimerState((prev) => ({ ...prev, isRunning: true }))
155
- }, 0)
156
- } else {
157
- startTimeRef.current = null
105
+ // Count-down mode
106
+ const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
107
+ const initialProgress =
108
+ elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
109
+
110
+ setTimerState((prev) => ({
111
+ ...prev,
112
+ remainingTime: remainingSeconds,
113
+ currentProgress: initialProgress, // Immediately set progress
114
+ }))
115
+
116
+ progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
117
+
118
+ if (remainingSeconds === 0) {
119
+ setTimerState((prev) => ({ ...prev, isRunning: false }))
120
+ startTimeRef.current = null
121
+ if (onCycleEnd) {
122
+ queueMicrotask(() => onCycleEnd())
158
123
  }
124
+ } else if (autoStart) {
125
+ setTimeout(() => {
126
+ startTimeRef.current = Date.now() - elapsedSeconds * 1000
127
+ setTimerState((prev) => ({ ...prev, isRunning: true }))
128
+ }, 0)
159
129
  } else {
160
- // Count-up mode
161
- const initialProgress = ((elapsedSeconds / 60) % 1) * 100
162
- setTimerState((prev) => ({
163
- ...prev,
164
- remainingTime: elapsedSeconds,
165
- currentProgress: initialProgress, // Immediately set progress
166
- }))
167
-
168
- progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
169
-
170
- if (autoStart) {
171
- setTimeout(() => {
172
- startTimeRef.current = Date.now() - elapsedSeconds * 1000
173
- setTimerState((prev) => ({ ...prev, isRunning: true }))
174
- }, 0)
175
- } else {
176
- startTimeRef.current = null
177
- }
130
+ startTimeRef.current = null
178
131
  }
179
132
  },
180
133
  [autoStart, onCycleEnd, progressInterpolator],
181
134
  )
182
135
 
183
136
  const completeMeasuring = useCallback(() => {
184
- if (timerState.currentState === "measuring") {
185
- setTimerState((prev) => ({
186
- ...prev,
187
- isRunning: false,
188
- currentState: "measured",
189
- }))
190
- startTimeRef.current = null
137
+ // Always trigger completion regardless of current state
138
+ setTimerState((prev) => ({
139
+ ...prev,
140
+ isRunning: false,
141
+ currentState: "measured",
142
+ }))
143
+ startTimeRef.current = null
191
144
 
192
- onStartPulsating(() => {
193
- if (onMeasuringComplete) {
194
- onMeasuringComplete()
195
- }
196
- })
197
- }
198
- }, [timerState.currentState, onStartPulsating, onMeasuringComplete])
145
+ onStartPulsating(() => {
146
+ if (onMeasuringComplete) {
147
+ onMeasuringComplete()
148
+ }
149
+ })
150
+ }, [onStartPulsating, onMeasuringComplete])
199
151
 
200
152
  const pause = useCallback(() => {
201
153
  if (startTimeRef.current && timerState.isRunning) {
@@ -316,13 +268,6 @@ export const useTimerLogic = ({
316
268
  }))
317
269
  const progress = ((elapsed / 60) % 1) * 100
318
270
  progressInterpolator.setTarget([progress])
319
- } else if (timerState.currentState === "countup") {
320
- setTimerState((prev) => ({
321
- ...prev,
322
- remainingTime: Math.floor(elapsed),
323
- }))
324
- const progress = ((elapsed / 60) % 1) * 100
325
- progressInterpolator.setTarget([progress])
326
271
  }
327
272
 
328
273
  if (timerState.isRunning) {
@@ -375,7 +320,6 @@ export const useTimerLogic = ({
375
320
  controls: {
376
321
  startNewCycle,
377
322
  startMeasuring,
378
- startCountUp,
379
323
  setIdle,
380
324
  completeMeasuring,
381
325
  pause,
@@ -25,8 +25,8 @@ export const calculateProgress = (
25
25
  return Math.min(100, (elapsed / maxTime) * 100)
26
26
  }
27
27
 
28
- if (currentState === "measuring" || currentState === "countup") {
29
- // Count-up modes: progress based on minute steps (0-100% per minute)
28
+ if (currentState === "measuring") {
29
+ // Measuring mode: progress based on minute steps (0-100% per minute)
30
30
  return ((remainingTime / 60) % 1) * 100
31
31
  }
32
32
 
@@ -45,7 +45,7 @@ export const calculateExactProgress = (
45
45
  return Math.min(100, (totalElapsed / maxTime) * 100)
46
46
  }
47
47
 
48
- if (currentState === "measuring" || currentState === "countup") {
48
+ if (currentState === "measuring") {
49
49
  return ((totalElapsed / 60) % 1) * 100
50
50
  }
51
51
 
@@ -6,9 +6,15 @@ import { externalizeComponent } from "../externalizeComponent"
6
6
 
7
7
  export enum ProgramState {
8
8
  IDLE = "idle",
9
+ PREPARING = "preparing",
10
+ STARTING = "starting",
9
11
  RUNNING = "running",
12
+ PAUSING = "pausing",
10
13
  PAUSED = "paused",
11
14
  STOPPING = "stopping",
15
+ COMPLETED = "completed",
16
+ FAILED = "failed",
17
+ STOPPED = "stopped",
12
18
  ERROR = "error",
13
19
  }
14
20
 
@@ -52,7 +58,7 @@ interface ButtonConfig {
52
58
  * A control component for program execution with run, pause, and stop functionality.
53
59
  *
54
60
  * Features:
55
- * - State machine with idle, running, paused, stopping, and error states
61
+ * - State machine with idle, preparing, starting, running, pausing, paused, stopping, completed, failed, stopped, and error states
56
62
  * - Two variants: with_pause (3 buttons) and without_pause (2 buttons)
57
63
  * - Optional manual reset functionality
58
64
  * - Responsive design with 110px circular buttons
@@ -78,12 +84,15 @@ export const ProgramControl = externalizeComponent(
78
84
  run: {
79
85
  enabled:
80
86
  state === ProgramState.IDLE ||
87
+ state === ProgramState.STOPPED ||
81
88
  state === ProgramState.PAUSED ||
89
+ state === ProgramState.COMPLETED ||
90
+ state === ProgramState.FAILED ||
82
91
  state === ProgramState.ERROR,
83
92
  label:
84
93
  state === ProgramState.PAUSED
85
94
  ? t("ProgramControl.Resume.bt")
86
- : state === ProgramState.ERROR
95
+ : state === ProgramState.ERROR || state === ProgramState.FAILED
87
96
  ? t("ProgramControl.Retry.bt")
88
97
  : t("ProgramControl.Start.bt"),
89
98
  color: theme.palette.success.main,
@@ -97,7 +106,11 @@ export const ProgramControl = externalizeComponent(
97
106
  },
98
107
  stop: {
99
108
  enabled:
100
- state === ProgramState.RUNNING || state === ProgramState.PAUSED,
109
+ state === ProgramState.PREPARING ||
110
+ state === ProgramState.STARTING ||
111
+ state === ProgramState.RUNNING ||
112
+ state === ProgramState.PAUSING ||
113
+ state === ProgramState.PAUSED,
101
114
  label: t("ProgramControl.Stop.bt"),
102
115
  color: theme.palette.error.main,
103
116
  onClick: onStop,
@@ -169,6 +182,9 @@ export const ProgramControl = externalizeComponent(
169
182
  variant="contained"
170
183
  disabled={
171
184
  !config.enabled ||
185
+ state === ProgramState.PREPARING ||
186
+ state === ProgramState.STARTING ||
187
+ state === ProgramState.PAUSING ||
172
188
  (state === ProgramState.STOPPING && !requiresManualReset)
173
189
  }
174
190
  onClick={config.onClick}
@@ -179,6 +195,9 @@ export const ProgramControl = externalizeComponent(
179
195
  backgroundColor: config.color,
180
196
  opacity:
181
197
  config.enabled &&
198
+ state !== ProgramState.PREPARING &&
199
+ state !== ProgramState.STARTING &&
200
+ state !== ProgramState.PAUSING &&
182
201
  !(state === ProgramState.STOPPING && !requiresManualReset)
183
202
  ? 1
184
203
  : 0.3,
@@ -186,6 +205,9 @@ export const ProgramControl = externalizeComponent(
186
205
  backgroundColor: config.color,
187
206
  opacity:
188
207
  config.enabled &&
208
+ state !== ProgramState.PREPARING &&
209
+ state !== ProgramState.STARTING &&
210
+ state !== ProgramState.PAUSING &&
189
211
  !(
190
212
  state === ProgramState.STOPPING &&
191
213
  !requiresManualReset
@@ -209,12 +231,18 @@ export const ProgramControl = externalizeComponent(
209
231
  sx={{
210
232
  color:
211
233
  config.enabled &&
234
+ state !== ProgramState.PREPARING &&
235
+ state !== ProgramState.STARTING &&
236
+ state !== ProgramState.PAUSING &&
212
237
  !(state === ProgramState.STOPPING && !requiresManualReset)
213
238
  ? config.color
214
239
  : theme.palette.text.disabled,
215
240
  textAlign: "center",
216
241
  opacity:
217
242
  config.enabled &&
243
+ state !== ProgramState.PREPARING &&
244
+ state !== ProgramState.STARTING &&
245
+ state !== ProgramState.PAUSING &&
218
246
  !(state === ProgramState.STOPPING && !requiresManualReset)
219
247
  ? 1
220
248
  : 0.3,
@@ -77,11 +77,26 @@ export const ProgramStateIndicator = externalizeComponent(
77
77
  // For normal safety states, check program state
78
78
  if (safetyState === "SAFETY_STATE_NORMAL") {
79
79
  switch (programState) {
80
+ case ProgramState.PREPARING:
81
+ return {
82
+ label: t("ProgramStateIndicator.Preparing.lb"),
83
+ color: theme.palette.warning.main,
84
+ }
85
+ case ProgramState.STARTING:
86
+ return {
87
+ label: t("ProgramStateIndicator.Starting.lb"),
88
+ color: theme.palette.warning.main,
89
+ }
80
90
  case ProgramState.RUNNING:
81
91
  return {
82
92
  label: t("ProgramStateIndicator.Running.lb"),
83
93
  color: theme.palette.success.main,
84
94
  }
95
+ case ProgramState.PAUSING:
96
+ return {
97
+ label: t("ProgramStateIndicator.Pausing.lb"),
98
+ color: theme.palette.warning.main,
99
+ }
85
100
  case ProgramState.PAUSED:
86
101
  return {
87
102
  label: t("ProgramStateIndicator.Paused.lb"),
@@ -89,9 +104,24 @@ export const ProgramStateIndicator = externalizeComponent(
89
104
  }
90
105
  case ProgramState.STOPPING:
91
106
  return {
92
- label: t("ProgramStateIndicator.Stopped.lb"),
107
+ label: t("ProgramStateIndicator.Stopping.lb"),
108
+ color: theme.palette.warning.main,
109
+ }
110
+ case ProgramState.COMPLETED:
111
+ return {
112
+ label: t("ProgramStateIndicator.Completed.lb"),
113
+ color: theme.palette.success.main,
114
+ }
115
+ case ProgramState.FAILED:
116
+ return {
117
+ label: t("ProgramStateIndicator.Failed.lb"),
93
118
  color: theme.palette.error.main,
94
119
  }
120
+ case ProgramState.STOPPED:
121
+ return {
122
+ label: t("ProgramStateIndicator.Stopped.lb"),
123
+ color: theme.palette.grey[600],
124
+ }
95
125
  case ProgramState.ERROR:
96
126
  return {
97
127
  label: t("ProgramStateIndicator.Error.lb"),
@@ -0,0 +1,2 @@
1
+ export { Timer } from "./index"
2
+ export type { TimerControls, TimerProps } from "./types"
@@ -0,0 +1,140 @@
1
+ import { Box, Fade, Typography } from "@mui/material"
2
+ import { Gauge } from "@mui/x-charts"
3
+ import { useTheme } from "@mui/material/styles"
4
+ import { useTranslation } from "react-i18next"
5
+ import type { TimerState, TimerAnimationState } from "./types"
6
+ import { formatTime } from "./utils"
7
+
8
+ interface TimerDefaultVariantProps {
9
+ timerState: TimerState
10
+ animationState: TimerAnimationState
11
+ hasError: boolean
12
+ className?: string
13
+ }
14
+
15
+ export const TimerDefaultVariant = ({
16
+ timerState,
17
+ animationState,
18
+ hasError,
19
+ className,
20
+ }: TimerDefaultVariantProps) => {
21
+ const { t } = useTranslation()
22
+ const theme = useTheme()
23
+ const { elapsedTime, currentProgress } = timerState
24
+ const { showErrorAnimation, showPauseAnimation, showMainText } = animationState
25
+
26
+ return (
27
+ <Box
28
+ className={className}
29
+ sx={{
30
+ position: "relative",
31
+ width: 264,
32
+ height: 264,
33
+ display: "flex",
34
+ alignItems: "center",
35
+ justifyContent: "center",
36
+ }}
37
+ >
38
+ <Gauge
39
+ width={264}
40
+ height={264}
41
+ value={currentProgress}
42
+ valueMin={0}
43
+ valueMax={100}
44
+ innerRadius="85%"
45
+ outerRadius="100%"
46
+ margin={0}
47
+ skipAnimation={true}
48
+ text={() => ""}
49
+ sx={{
50
+ opacity: showPauseAnimation || showErrorAnimation ? 0.6 : 1,
51
+ transition: "opacity 0.5s ease-out",
52
+ [`& .MuiGauge-valueArc`]: {
53
+ fill: hasError
54
+ ? theme.palette.error.light
55
+ : theme.palette.success.main,
56
+ transition: "fill 0.5s ease-out",
57
+ },
58
+ [`& .MuiGauge-referenceArc`]: {
59
+ fill: "#171927",
60
+ stroke: "transparent",
61
+ strokeWidth: 0,
62
+ transition:
63
+ "fill 0.5s ease-out, stroke 0.5s ease-out, stroke-width 0.5s ease-out",
64
+ },
65
+ [`& .MuiGauge-valueText`]: {
66
+ display: "none",
67
+ },
68
+ [`& .MuiGauge-text`]: {
69
+ display: "none",
70
+ },
71
+ }}
72
+ />
73
+
74
+ {/* Center content overlay */}
75
+ <Box
76
+ sx={{
77
+ position: "absolute",
78
+ top: "50%",
79
+ left: "50%",
80
+ transform: "translate(-50%, -50%)",
81
+ width: 225,
82
+ height: 225,
83
+ borderRadius: "50%",
84
+ backgroundColor: "#292B3F",
85
+ display: "flex",
86
+ flexDirection: "column",
87
+ alignItems: "center",
88
+ justifyContent: "center",
89
+ textAlign: "center",
90
+ gap: 1,
91
+ transition: "background-color 0.5s ease-out",
92
+ }}
93
+ >
94
+ {/* Main display */}
95
+ <Box
96
+ sx={{
97
+ position: "relative",
98
+ height: "48px",
99
+ display: "flex",
100
+ alignItems: "center",
101
+ justifyContent: "center",
102
+ marginBottom: 0.5,
103
+ }}
104
+ >
105
+ {/* Error text */}
106
+ <Fade in={showMainText && hasError} timeout={200}>
107
+ <Typography
108
+ variant="h6"
109
+ sx={{
110
+ position: "absolute",
111
+ fontSize: "16px",
112
+ fontWeight: 500,
113
+ color: theme.palette.error.light,
114
+ }}
115
+ >
116
+ {t("timer.error")}
117
+ </Typography>
118
+ </Fade>
119
+
120
+ {/* Timer display */}
121
+ <Fade in={showMainText && !hasError} timeout={300}>
122
+ <Typography
123
+ variant="h1"
124
+ sx={{
125
+ position: "absolute",
126
+ fontSize: "48px",
127
+ fontWeight: 500,
128
+ color: theme.palette.text.primary,
129
+ lineHeight: 1,
130
+ letterSpacing: "-0.5px",
131
+ }}
132
+ >
133
+ {formatTime(elapsedTime)}
134
+ </Typography>
135
+ </Fade>
136
+ </Box>
137
+ </Box>
138
+ </Box>
139
+ )
140
+ }