@wandelbots/wandelbots-js-react-components 2.34.1-pr.feature-robot-precondition-list.372.d8a5663 → 2.34.1-pr.fix-kr30-glb-model.375.6739762

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 (65) hide show
  1. package/dist/components/CycleTimer.d.ts +16 -33
  2. package/dist/components/CycleTimer.d.ts.map +1 -1
  3. package/dist/components/ProgramControl.d.ts +2 -8
  4. package/dist/components/ProgramControl.d.ts.map +1 -1
  5. package/dist/components/ProgramStateIndicator.d.ts +1 -1
  6. package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
  7. package/dist/components/robots/Robot.d.ts +2 -3
  8. package/dist/components/robots/Robot.d.ts.map +1 -1
  9. package/dist/icons/index.d.ts +0 -1
  10. package/dist/icons/index.d.ts.map +1 -1
  11. package/dist/index.cjs +49 -49
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +0 -10
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7512 -9338
  16. package/dist/index.js.map +1 -1
  17. package/dist/themes/createDarkTheme.d.ts.map +1 -1
  18. package/package.json +1 -2
  19. package/src/components/CycleTimer.tsx +148 -277
  20. package/src/components/ProgramControl.tsx +12 -27
  21. package/src/components/ProgramStateIndicator.tsx +8 -25
  22. package/src/components/robots/Robot.tsx +2 -5
  23. package/src/i18n/locales/de/translations.json +1 -7
  24. package/src/i18n/locales/en/translations.json +1 -7
  25. package/src/icons/index.ts +0 -1
  26. package/src/index.ts +0 -14
  27. package/src/themes/createDarkTheme.ts +1 -75
  28. package/dist/components/AppHeader.d.ts +0 -34
  29. package/dist/components/AppHeader.d.ts.map +0 -1
  30. package/dist/components/DataGrid.d.ts +0 -66
  31. package/dist/components/DataGrid.d.ts.map +0 -1
  32. package/dist/components/LogPanel.d.ts +0 -38
  33. package/dist/components/LogPanel.d.ts.map +0 -1
  34. package/dist/components/LogStore.d.ts +0 -12
  35. package/dist/components/LogStore.d.ts.map +0 -1
  36. package/dist/components/LogViewer.d.ts +0 -46
  37. package/dist/components/LogViewer.d.ts.map +0 -1
  38. package/dist/components/RobotCard.d.ts +0 -103
  39. package/dist/components/RobotCard.d.ts.map +0 -1
  40. package/dist/components/RobotListItem.d.ts +0 -34
  41. package/dist/components/RobotListItem.d.ts.map +0 -1
  42. package/dist/components/RobotSetupReadinessIndicator.d.ts +0 -31
  43. package/dist/components/RobotSetupReadinessIndicator.d.ts.map +0 -1
  44. package/dist/components/RobotSetupReadinessIndicator.test.d.ts +0 -2
  45. package/dist/components/RobotSetupReadinessIndicator.test.d.ts.map +0 -1
  46. package/dist/components/TabBar.d.ts +0 -30
  47. package/dist/components/TabBar.d.ts.map +0 -1
  48. package/dist/components/robots/manufacturerHomePositions.d.ts +0 -21
  49. package/dist/components/robots/manufacturerHomePositions.d.ts.map +0 -1
  50. package/dist/icons/DropdownArrowIcon.d.ts +0 -3
  51. package/dist/icons/DropdownArrowIcon.d.ts.map +0 -1
  52. package/src/components/AppHeader.md +0 -84
  53. package/src/components/AppHeader.tsx +0 -199
  54. package/src/components/DataGrid.tsx +0 -659
  55. package/src/components/LogPanel.tsx +0 -69
  56. package/src/components/LogStore.ts +0 -44
  57. package/src/components/LogViewer.tsx +0 -370
  58. package/src/components/RobotCard.tsx +0 -596
  59. package/src/components/RobotListItem.tsx +0 -150
  60. package/src/components/RobotSetupReadinessIndicator.test.tsx +0 -60
  61. package/src/components/RobotSetupReadinessIndicator.tsx +0 -124
  62. package/src/components/TabBar.tsx +0 -144
  63. package/src/components/robots/manufacturerHomePositions.ts +0 -76
  64. package/src/icons/DropdownArrowIcon.tsx +0 -13
  65. package/src/icons/chevronDown.svg +0 -3
@@ -1 +1 @@
1
- {"version":3,"file":"createDarkTheme.d.ts","sourceRoot":"","sources":["../../src/themes/createDarkTheme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,eAAe,CAAA;AAEjE,wBAAgB,eAAe,IAAI,KAAK,CA0WvC"}
1
+ {"version":3,"file":"createDarkTheme.d.ts","sourceRoot":"","sources":["../../src/themes/createDarkTheme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,eAAe,CAAA;AAEjE,wBAAgB,eAAe,IAAI,KAAK,CAgSvC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wandelbots/wandelbots-js-react-components",
3
- "version": "2.34.1-pr.feature-robot-precondition-list.372.d8a5663",
3
+ "version": "2.34.1-pr.fix-kr30-glb-model.375.6739762",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -139,7 +139,6 @@
139
139
  "dependencies": {
140
140
  "@monaco-editor/react": "^4.7.0",
141
141
  "@mui/x-charts": "^8.9.0",
142
- "@mui/x-data-grid": "^8.10.1",
143
142
  "@shikijs/monaco": "^3.1.0",
144
143
  "i18next-browser-languagedetector": "^8.0.4",
145
144
  "lodash-es": "^4.17.21",
@@ -1,4 +1,4 @@
1
- import { Box, Fade, Typography, useTheme } from "@mui/material"
1
+ import { Box, Typography, useTheme } from "@mui/material"
2
2
  import { Gauge } from "@mui/x-charts/Gauge"
3
3
  import { observer } from "mobx-react-lite"
4
4
  import { useCallback, useEffect, useRef, useState } from "react"
@@ -9,13 +9,13 @@ import { useInterpolation } from "./utils/interpolation"
9
9
  export interface CycleTimerProps {
10
10
  /**
11
11
  * Callback that receives the timer control functions:
12
- * - `startNewCycle(maxTimeSeconds?, elapsedSeconds?)` - Start a new timer cycle (if maxTimeSeconds is omitted, runs as count-up timer)
12
+ * - `startNewCycle(maxTimeSeconds, elapsedSeconds?)` - Start a new timer cycle
13
13
  * - `pause()` - Pause the countdown while preserving remaining time
14
14
  * - `resume()` - Resume countdown from where it was paused
15
15
  * - `isPaused()` - Check current pause state
16
16
  */
17
17
  onCycleComplete: (controls: {
18
- startNewCycle: (maxTimeSeconds?: number, elapsedSeconds?: number) => void
18
+ startNewCycle: (maxTimeSeconds: number, elapsedSeconds?: number) => void
19
19
  pause: () => void
20
20
  resume: () => void
21
21
  isPaused: () => boolean
@@ -30,23 +30,18 @@ export interface CycleTimerProps {
30
30
  compact?: boolean
31
31
  /** Additional CSS classes */
32
32
  className?: string
33
- /** Whether the timer is in an error state (pauses timer and shows error styling) */
34
- hasError?: boolean
35
33
  }
36
34
 
37
35
  /**
38
- * A circular gauge timer component that shows the remaining time of a cycle or counts up
36
+ * A circular gauge timer component that shows the remaining time of a cycle
39
37
  *
40
38
  * Features:
41
39
  * - Circular gauge with 264px diameter and 40px thickness
42
- * - Two modes: count-down (with max time) or count-up (without max time)
43
- * - Count-down mode: shows remaining time prominently, counts down to zero
44
- * - Count-up mode: shows elapsed time, gauge progresses in minute steps
45
- * - Displays appropriate labels based on mode
46
- * - Automatically counts down/up and triggers callback when reaching zero (count-down only)
40
+ * - Shows remaining time prominently in the center (60px font)
41
+ * - Displays "remaining time" label at top and total time at bottom
42
+ * - Automatically counts down and triggers callback when reaching zero
47
43
  * - Full timer control: start, pause, resume functionality
48
44
  * - Support for starting with elapsed time (resume mid-cycle)
49
- * - Error state support: pauses timer and shows error styling (red color)
50
45
  * - Smooth spring-based progress animations for all state transitions
51
46
  * - Fully localized with i18next
52
47
  * - Material-UI theming integration
@@ -58,44 +53,32 @@ export interface CycleTimerProps {
58
53
  * @param variant - Visual variant: "default" (large gauge) or "small" (animated icon with text)
59
54
  * @param compact - For small variant: whether to hide remaining time details
60
55
  * @param className - Additional CSS classes
61
- * @param hasError - Whether the timer is in an error state (pauses timer and shows error styling)
62
56
  *
63
57
  * Usage:
64
58
  * ```tsx
65
- * // Count-down timer (with max time)
66
59
  * <CycleTimer
67
60
  * onCycleComplete={(controls) => {
68
- * // Start a 5-minute countdown cycle
61
+ * // Start a 5-minute cycle
69
62
  * controls.startNewCycle(300)
70
63
  *
71
64
  * // Or start a 5-minute cycle with 2 minutes already elapsed
72
65
  * controls.startNewCycle(300, 120)
73
- * }}
74
- * onCycleEnd={() => console.log('Cycle completed!')}
75
- * />
76
66
  *
77
- * // Count-up timer (no max time)
78
- * <CycleTimer
79
- * onCycleComplete={(controls) => {
80
- * // Start count-up timer
81
- * controls.startNewCycle()
67
+ * // Pause the timer
68
+ * controls.pause()
82
69
  *
83
- * // Or start count-up timer with some elapsed time
84
- * controls.startNewCycle(undefined, 120)
85
- * }}
86
- * />
70
+ * // Resume the timer
71
+ * controls.resume()
87
72
  *
88
- * // Timer with error state
89
- * <CycleTimer
90
- * hasError={errorCondition}
91
- * onCycleComplete={(controls) => {
92
- * controls.startNewCycle(300)
73
+ * // Check if paused
74
+ * const paused = controls.isPaused()
93
75
  * }}
76
+ * onCycleEnd={() => console.log('Cycle completed!')}
94
77
  * />
95
78
  * ```
96
79
  *
97
80
  * Control Functions:
98
- * - `startNewCycle(maxTimeSeconds?, elapsedSeconds?)` - Start a new timer cycle (omit maxTimeSeconds for count-up mode)
81
+ * - `startNewCycle(maxTimeSeconds, elapsedSeconds?)` - Start a new timer cycle
99
82
  * - `pause()` - Pause the countdown while preserving remaining time
100
83
  * - `resume()` - Resume countdown from where it was paused
101
84
  * - `isPaused()` - Check current pause state
@@ -109,23 +92,17 @@ export const CycleTimer = externalizeComponent(
109
92
  variant = "default",
110
93
  compact = false,
111
94
  className,
112
- hasError = false,
113
95
  }: CycleTimerProps) => {
114
96
  const theme = useTheme()
115
97
  const { t } = useTranslation()
116
98
  const [remainingTime, setRemainingTime] = useState(0)
117
- const [maxTime, setMaxTime] = useState<number | null>(null)
99
+ const [maxTime, setMaxTime] = useState(0)
118
100
  const [isRunning, setIsRunning] = useState(false)
119
101
  const [isPausedState, setIsPausedState] = useState(false)
120
102
  const [currentProgress, setCurrentProgress] = useState(0)
121
103
  const animationRef = useRef<number | null>(null)
122
104
  const startTimeRef = useRef<number | null>(null)
123
105
  const pausedTimeRef = useRef<number>(0)
124
- const [wasRunningBeforeError, setWasRunningBeforeError] = useState(false)
125
-
126
- // Track mode changes for fade transitions
127
- const [showLabels, setShowLabels] = useState(true)
128
- const prevMaxTimeRef = useRef<number | null | undefined>(undefined)
129
106
 
130
107
  // Spring-based interpolator for smooth gauge progress animations
131
108
  // Uses physics simulation to create natural, smooth transitions between progress values
@@ -137,78 +114,37 @@ export const CycleTimer = externalizeComponent(
137
114
  },
138
115
  })
139
116
 
140
- // Handle mode changes with fade transitions for labels only
141
- useEffect(() => {
142
- const currentIsCountUp = maxTime === null
143
- const prevMaxTime = prevMaxTimeRef.current
144
- const prevIsCountUp = prevMaxTime === null
145
-
146
- // Check if mode actually changed (not just first render)
147
- if (
148
- prevMaxTimeRef.current !== undefined &&
149
- prevIsCountUp !== currentIsCountUp
150
- ) {
151
- // Mode changed - labels will fade based on the Fade component conditions
152
- // We just need to ensure showLabels is true so Fade can control visibility
153
- setShowLabels(true)
154
- } else {
155
- // No mode change or first time - set initial state
156
- setShowLabels(true)
157
- }
158
-
159
- prevMaxTimeRef.current = maxTime
160
- }, [maxTime])
161
-
162
117
  const startNewCycle = useCallback(
163
- (maxTimeSeconds?: number, elapsedSeconds: number = 0) => {
164
- setMaxTime(maxTimeSeconds ?? null)
118
+ (maxTimeSeconds: number, elapsedSeconds: number = 0) => {
119
+ setMaxTime(maxTimeSeconds)
120
+ const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
121
+ setRemainingTime(remainingSeconds)
165
122
  setIsPausedState(false)
166
123
  pausedTimeRef.current = 0
167
124
 
168
- if (maxTimeSeconds !== undefined) {
169
- // Count-down mode: set remaining time
170
- const remainingSeconds = Math.max(
171
- 0,
172
- maxTimeSeconds - elapsedSeconds,
173
- )
174
- setRemainingTime(remainingSeconds)
175
-
176
- // Animate progress smoothly to starting position
177
- const initialProgress =
178
- elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
179
- if (elapsedSeconds === 0) {
180
- progressInterpolator.setTarget([0])
181
- } else {
182
- progressInterpolator.setTarget([initialProgress])
183
- }
184
-
185
- if (remainingSeconds === 0) {
186
- setIsRunning(false)
187
- startTimeRef.current = null
188
- // Trigger completion callback immediately if time is already up
189
- if (onCycleEnd) {
190
- setTimeout(() => onCycleEnd(), 0)
191
- }
192
- } else if (autoStart) {
193
- startTimeRef.current = Date.now() - elapsedSeconds * 1000
194
- setIsRunning(true)
195
- } else {
196
- startTimeRef.current = null
197
- }
125
+ // Animate progress smoothly to starting position
126
+ // For new cycles (no elapsed time), animate from current position to 0%
127
+ // For resumed cycles, animate to the appropriate progress percentage
128
+ const initialProgress =
129
+ elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
130
+ if (elapsedSeconds === 0) {
131
+ progressInterpolator.setTarget([0])
198
132
  } else {
199
- // Count-up mode: start from elapsed time
200
- setRemainingTime(elapsedSeconds)
201
-
202
- // For count-up mode, progress is based on minute steps
203
- const initialProgress = ((elapsedSeconds / 60) % 1) * 100
204
133
  progressInterpolator.setTarget([initialProgress])
134
+ }
205
135
 
206
- if (autoStart) {
207
- startTimeRef.current = Date.now() - elapsedSeconds * 1000
208
- setIsRunning(true)
209
- } else {
210
- startTimeRef.current = null
136
+ if (remainingSeconds === 0) {
137
+ setIsRunning(false)
138
+ startTimeRef.current = null
139
+ // Trigger completion callback immediately if time is already up
140
+ if (onCycleEnd) {
141
+ setTimeout(() => onCycleEnd(), 0)
211
142
  }
143
+ } else if (autoStart) {
144
+ startTimeRef.current = Date.now() - elapsedSeconds * 1000
145
+ setIsRunning(true)
146
+ } else {
147
+ startTimeRef.current = null
212
148
  }
213
149
  },
214
150
  [autoStart, onCycleEnd, progressInterpolator],
@@ -223,16 +159,8 @@ export const CycleTimer = externalizeComponent(
223
159
  // Calculate exact progress position and smoothly animate to it when pausing
224
160
  // This ensures the visual progress matches the actual elapsed time
225
161
  const totalElapsed = pausedTimeRef.current / 1000
226
-
227
- if (maxTime !== null) {
228
- // Count-down mode
229
- const exactProgress = Math.min(100, (totalElapsed / maxTime) * 100)
230
- progressInterpolator.setTarget([exactProgress])
231
- } else {
232
- // Count-up mode: progress based on minute steps
233
- const exactProgress = ((totalElapsed / 60) % 1) * 100
234
- progressInterpolator.setTarget([exactProgress])
235
- }
162
+ const exactProgress = Math.min(100, (totalElapsed / maxTime) * 100)
163
+ progressInterpolator.setTarget([exactProgress])
236
164
  }
237
165
  setIsRunning(false)
238
166
  setIsPausedState(true)
@@ -250,30 +178,6 @@ export const CycleTimer = externalizeComponent(
250
178
  return isPausedState
251
179
  }, [isPausedState])
252
180
 
253
- // Handle error state changes
254
- useEffect(() => {
255
- if (hasError) {
256
- // Error occurred - pause timer if running and remember state
257
- if (isRunning && !isPausedState) {
258
- setWasRunningBeforeError(true)
259
- pause()
260
- }
261
- } else {
262
- // Error resolved - resume if was running before error
263
- if (wasRunningBeforeError && isPausedState) {
264
- setWasRunningBeforeError(false)
265
- resume()
266
- }
267
- }
268
- }, [
269
- hasError,
270
- isRunning,
271
- isPausedState,
272
- wasRunningBeforeError,
273
- pause,
274
- resume,
275
- ])
276
-
277
181
  // Call onCycleComplete immediately to provide the timer control functions
278
182
  useEffect(() => {
279
183
  let isMounted = true
@@ -298,39 +202,30 @@ export const CycleTimer = externalizeComponent(
298
202
  if (isRunning) {
299
203
  // Single animation frame loop that handles both time updates and progress
300
204
  const updateTimer = () => {
301
- if (startTimeRef.current) {
205
+ if (startTimeRef.current && maxTime > 0) {
302
206
  const now = Date.now()
303
207
  const elapsed =
304
208
  (now - startTimeRef.current + pausedTimeRef.current) / 1000
305
-
306
- if (maxTime !== null) {
307
- // Count-down mode
308
- const remaining = Math.max(0, maxTime - elapsed)
309
- setRemainingTime(Math.ceil(remaining))
310
-
311
- // Smoothly animate progress based on elapsed time for fluid visual feedback
312
- const progress = Math.min(100, (elapsed / maxTime) * 100)
313
- progressInterpolator.setTarget([progress])
314
-
315
- if (remaining <= 0) {
316
- setIsRunning(false)
317
- startTimeRef.current = null
318
- setRemainingTime(0)
319
- // Animate to 100% completion with smooth spring transition
320
- progressInterpolator.setTarget([100])
321
- // Call onCycleEnd when timer reaches zero to notify about completion
322
- if (onCycleEnd) {
323
- setTimeout(() => onCycleEnd(), 0)
324
- }
325
- return
209
+ const remaining = Math.max(0, maxTime - elapsed)
210
+
211
+ // Update remaining time based on timestamp calculation
212
+ setRemainingTime(Math.ceil(remaining))
213
+
214
+ // Smoothly animate progress based on elapsed time for fluid visual feedback
215
+ const progress = Math.min(100, (elapsed / maxTime) * 100)
216
+ progressInterpolator.setTarget([progress])
217
+
218
+ if (remaining <= 0) {
219
+ setIsRunning(false)
220
+ startTimeRef.current = null
221
+ setRemainingTime(0)
222
+ // Animate to 100% completion with smooth spring transition
223
+ progressInterpolator.setTarget([100])
224
+ // Call onCycleEnd when timer reaches zero to notify about completion
225
+ if (onCycleEnd) {
226
+ setTimeout(() => onCycleEnd(), 0)
326
227
  }
327
- } else {
328
- // Count-up mode
329
- setRemainingTime(Math.floor(elapsed))
330
-
331
- // For count-up mode, progress completes every minute (0-100% per minute)
332
- const progress = ((elapsed / 60) % 1) * 100
333
- progressInterpolator.setTarget([progress])
228
+ return
334
229
  }
335
230
 
336
231
  // Continue animation loop while running
@@ -377,16 +272,9 @@ export const CycleTimer = externalizeComponent(
377
272
  // Keep interpolator synchronized with static progress when timer is stopped
378
273
  // Ensures correct visual state when component initializes or timer stops
379
274
  useEffect(() => {
380
- if (!isRunning && !isPausedState) {
381
- if (maxTime !== null && maxTime > 0) {
382
- // Count-down mode
383
- const staticProgress = ((maxTime - remainingTime) / maxTime) * 100
384
- progressInterpolator.setTarget([staticProgress])
385
- } else if (maxTime === null) {
386
- // Count-up mode
387
- const staticProgress = ((remainingTime / 60) % 1) * 100
388
- progressInterpolator.setTarget([staticProgress])
389
- }
275
+ if (!isRunning && !isPausedState && maxTime > 0) {
276
+ const staticProgress = ((maxTime - remainingTime) / maxTime) * 100
277
+ progressInterpolator.setTarget([staticProgress])
390
278
  }
391
279
  }, [
392
280
  isRunning,
@@ -413,64 +301,77 @@ export const CycleTimer = externalizeComponent(
413
301
  sx={{
414
302
  display: "flex",
415
303
  alignItems: "center",
416
- m: 0,
417
- gap: 1, // 8px gap between circle and text
304
+ gap: 0.125, // Minimal gap - 1px
418
305
  }}
419
306
  >
420
- {/* Animated progress ring icon */}
307
+ {/* Animated progress gauge icon */}
421
308
  <Box
422
309
  sx={{
423
- width: 20,
424
- height: 20,
310
+ position: "relative",
311
+ width: 40,
312
+ height: 40,
425
313
  display: "flex",
426
314
  alignItems: "center",
427
315
  justifyContent: "center",
428
- opacity: isPausedState ? 0.6 : 1,
429
- transition: "opacity 0.2s ease",
316
+ borderRadius: "50%",
317
+ overflow: "visible",
430
318
  }}
431
319
  >
432
- <svg
433
- width="20"
434
- height="20"
435
- viewBox="0 0 20 20"
436
- style={{ transform: "rotate(-90deg)" }}
437
- role="img"
438
- aria-label="Timer progress"
439
- >
440
- {/* Background ring */}
441
- <circle
442
- cx="10"
443
- cy="10"
444
- r="8"
445
- fill="none"
446
- stroke={
447
- hasError
448
- ? theme.palette.error.light
449
- : theme.palette.success.main
450
- }
451
- strokeWidth="2"
452
- opacity={0.3}
453
- />
454
- {/* Progress ring */}
455
- <circle
456
- cx="10"
457
- cy="10"
458
- r="8"
459
- fill="none"
460
- stroke={
461
- hasError
462
- ? theme.palette.error.light
463
- : theme.palette.success.main
464
- }
465
- strokeWidth="2"
466
- strokeLinecap="round"
467
- strokeDasharray={`${2 * Math.PI * 8}`}
468
- strokeDashoffset={`${2 * Math.PI * 8 * (1 - progressValue / 100)}`}
469
- style={{
470
- transition: "stroke-dashoffset 0.1s ease-out",
471
- }}
472
- />
473
- </svg>
320
+ <Gauge
321
+ width={40}
322
+ height={40}
323
+ value={progressValue}
324
+ valueMin={0}
325
+ valueMax={100}
326
+ innerRadius="70%"
327
+ outerRadius="95%"
328
+ skipAnimation={true}
329
+ sx={{
330
+ opacity: isPausedState ? 0.6 : 1,
331
+ transition: "opacity 0.2s ease",
332
+ [`& .MuiGauge-valueArc`]: {
333
+ fill: theme.palette.success.main,
334
+ },
335
+ [`& .MuiGauge-referenceArc`]: {
336
+ fill: theme.palette.success.main,
337
+ opacity: 0.3,
338
+ },
339
+ [`& .MuiGauge-valueText`]: {
340
+ display: "none",
341
+ },
342
+ [`& .MuiGauge-text`]: {
343
+ display: "none",
344
+ },
345
+ [`& text`]: {
346
+ display: "none",
347
+ },
348
+ // Hide any inner circle elements that might flash
349
+ [`& .MuiGauge-referenceArcBackground`]: {
350
+ display: "none",
351
+ },
352
+ [`& .MuiGauge-valueArcBackground`]: {
353
+ display: "none",
354
+ },
355
+ [`& circle`]: {
356
+ display: "none",
357
+ },
358
+ }}
359
+ />
360
+
361
+ {/* Inner circle overlay to prevent flashing */}
362
+ <Box
363
+ sx={{
364
+ position: "absolute",
365
+ top: "50%",
366
+ left: "50%",
367
+ transform: "translate(-50%, -50%)",
368
+ width: 13,
369
+ height: 13,
370
+ borderRadius: "50%",
371
+ backgroundColor: theme.palette.background?.paper || "white",
372
+ pointerEvents: "none",
373
+ }}
374
+ />
474
375
  </Box>
475
376
 
476
377
  {/* Timer text display */}
@@ -481,15 +382,11 @@ export const CycleTimer = externalizeComponent(
481
382
  fontSize: "14px",
482
383
  }}
483
384
  >
484
- {maxTime !== null
485
- ? // Count-down mode: show remaining time
486
- compact
487
- ? // Compact mode: show remaining time with "min." suffix
488
- `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
489
- : // Full mode: show "remaining / of total min." format
490
- `${formatTime(remainingTime)} / ${t("CycleTimer.Time.lb", { time: formatTime(maxTime) })}`
491
- : // Count-up mode: show elapsed time only
492
- formatTime(remainingTime)}
385
+ {compact
386
+ ? // Compact mode: show only remaining time
387
+ formatTime(remainingTime)
388
+ : // Full mode: show "remaining / of total min." format
389
+ `${formatTime(remainingTime)} / ${t("CycleTimer.Time.lb", { time: formatTime(maxTime) })}`}
493
390
  </Typography>
494
391
  </Box>
495
392
  )
@@ -521,9 +418,7 @@ export const CycleTimer = externalizeComponent(
521
418
  opacity: isPausedState ? 0.6 : 1,
522
419
  transition: "opacity 0.2s ease",
523
420
  [`& .MuiGauge-valueArc`]: {
524
- fill: hasError
525
- ? theme.palette.error.light
526
- : theme.palette.success.main,
421
+ fill: theme.palette.success.main,
527
422
  },
528
423
  [`& .MuiGauge-referenceArc`]: {
529
424
  fill: "white",
@@ -551,30 +446,19 @@ export const CycleTimer = externalizeComponent(
551
446
  gap: 1,
552
447
  }}
553
448
  >
554
- {/* "remaining time" label - always reserves space to prevent layout shift */}
555
- <Box
449
+ {/* "remaining time" label */}
450
+ <Typography
451
+ variant="body2"
556
452
  sx={{
557
- height: "16px", // Fixed height to prevent layout shift
558
- display: "flex",
559
- alignItems: "center",
560
- justifyContent: "center",
453
+ fontSize: "12px",
454
+ color: theme.palette.text.secondary,
561
455
  marginBottom: 0.5,
562
456
  }}
563
457
  >
564
- <Fade in={showLabels && maxTime !== null} timeout={300}>
565
- <Typography
566
- variant="body2"
567
- sx={{
568
- fontSize: "12px",
569
- color: theme.palette.text.secondary,
570
- }}
571
- >
572
- {t("CycleTimer.RemainingTime.lb")}
573
- </Typography>
574
- </Fade>
575
- </Box>
458
+ {t("CycleTimer.RemainingTime.lb")}
459
+ </Typography>
576
460
 
577
- {/* Main timer display - never fades, always visible */}
461
+ {/* Main timer display */}
578
462
  <Typography
579
463
  variant="h1"
580
464
  sx={{
@@ -588,29 +472,16 @@ export const CycleTimer = externalizeComponent(
588
472
  {formatTime(remainingTime)}
589
473
  </Typography>
590
474
 
591
- {/* Total time display - always reserves space to prevent layout shift */}
592
- <Box
475
+ {/* Total time display */}
476
+ <Typography
477
+ variant="body2"
593
478
  sx={{
594
- height: "16px", // Fixed height to prevent layout shift
595
- display: "flex",
596
- alignItems: "center",
597
- justifyContent: "center",
479
+ fontSize: "12px",
480
+ color: theme.palette.text.secondary,
598
481
  }}
599
482
  >
600
- <Fade in={showLabels && maxTime !== null} timeout={300}>
601
- <Typography
602
- variant="body2"
603
- sx={{
604
- fontSize: "12px",
605
- color: theme.palette.text.secondary,
606
- }}
607
- >
608
- {maxTime !== null
609
- ? t("CycleTimer.OfTime.lb", { time: formatTime(maxTime) })
610
- : ""}
611
- </Typography>
612
- </Fade>
613
- </Box>
483
+ {t("CycleTimer.OfTime.lb", { time: formatTime(maxTime) })}
484
+ </Typography>
614
485
  </Box>
615
486
  </Box>
616
487
  )