@wandelbots/wandelbots-js-react-components 2.37.0-pr.feature-states-for-cycle-timer.379.4ca80c1 → 2.37.0-pr.feature-states-for-cycle-timer.379.b99a9af

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wandelbots/wandelbots-js-react-components",
3
- "version": "2.37.0-pr.feature-states-for-cycle-timer.379.4ca80c1",
3
+ "version": "2.37.0-pr.feature-states-for-cycle-timer.379.b99a9af",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -27,6 +27,8 @@ export const DefaultVariant = ({
27
27
  pulsatingFinished,
28
28
  showLabels,
29
29
  showMainText,
30
+ showIdlePulsating,
31
+ idleDotsCount,
30
32
  } = animationState
31
33
 
32
34
  return (
@@ -47,8 +49,9 @@ export const DefaultVariant = ({
47
49
  value={currentState === "idle" ? 0 : currentProgress}
48
50
  valueMin={0}
49
51
  valueMax={100}
50
- innerRadius={currentState === "idle" ? "75%" : "76%"}
51
- outerRadius="90%"
52
+ innerRadius="85%"
53
+ outerRadius="100%"
54
+ margin={0}
52
55
  skipAnimation={true}
53
56
  text={() => ""}
54
57
  sx={{
@@ -61,9 +64,9 @@ export const DefaultVariant = ({
61
64
  transition: "fill 0.5s ease-out",
62
65
  },
63
66
  [`& .MuiGauge-referenceArc`]: {
64
- fill: currentState === "idle" ? "#292B3F" : "white",
65
- stroke: currentState === "idle" ? "#181927" : "transparent",
66
- strokeWidth: currentState === "idle" ? 2 : 0,
67
+ fill: currentState === "idle" ? "#171927" : "#171927",
68
+ stroke: "transparent",
69
+ strokeWidth: 0,
67
70
  transition:
68
71
  "fill 0.5s ease-out, stroke 0.5s ease-out, stroke-width 0.5s ease-out",
69
72
  },
@@ -83,8 +86,6 @@ export const DefaultVariant = ({
83
86
  top: "50%",
84
87
  left: "50%",
85
88
  transform: "translate(-50%, -50%)",
86
- width: 200,
87
- height: 200,
88
89
  borderRadius: "50%",
89
90
  display: "flex",
90
91
  flexDirection: "column",
@@ -102,7 +103,7 @@ export const DefaultVariant = ({
102
103
  display: "flex",
103
104
  alignItems: "center",
104
105
  justifyContent: "center",
105
- marginBottom: 0.5,
106
+ marginBottom: 1,
106
107
  }}
107
108
  >
108
109
  <Fade
@@ -121,22 +122,32 @@ export const DefaultVariant = ({
121
122
  fontSize: "12px",
122
123
  color:
123
124
  currentState === "measured"
124
- ? pulsatingFinished
125
- ? theme.palette.text.secondary
126
- : showPulsatingText
127
- ? theme.palette.success.main
128
- : theme.palette.text.secondary
125
+ ? showPulsatingText || pulsatingFinished
126
+ ? theme.palette.success.main
127
+ : theme.palette.text.secondary
129
128
  : theme.palette.text.secondary,
130
129
  transition: "color 0.8s ease-in-out",
131
130
  }}
132
131
  >
133
- {currentState === "measuring"
134
- ? t("CycleTimer.CycleTime.lb", "Cycle Time")
135
- : currentState === "measured"
132
+ <span
133
+ style={{
134
+ opacity:
135
+ currentState === "measured" && pulsatingFinished
136
+ ? showPulsatingText
137
+ ? 1
138
+ : 0.6
139
+ : 1,
140
+ transition: "opacity 2s ease-in-out",
141
+ }}
142
+ >
143
+ {currentState === "measuring"
136
144
  ? t("CycleTimer.CycleTime.lb", "Cycle Time")
137
- : currentState === "countdown"
138
- ? t("CycleTimer.RemainingTime.lb", "Remaining Time")
139
- : ""}
145
+ : currentState === "measured"
146
+ ? t("CycleTimer.CycleTime.lb", "Cycle Time")
147
+ : currentState === "countdown"
148
+ ? t("CycleTimer.RemainingTime.lb", "Remaining Time")
149
+ : ""}
150
+ </span>
140
151
  </Typography>
141
152
  </Fade>
142
153
  </Box>
@@ -167,11 +178,35 @@ export const DefaultVariant = ({
167
178
  lineHeight: "166%",
168
179
  letterSpacing: "0.17px",
169
180
  textAlign: "center",
170
- width: "150px",
181
+ width: "200px",
171
182
  height: "20px",
183
+ display: "flex",
184
+ alignItems: "center",
185
+ justifyContent: "center",
172
186
  }}
173
187
  >
174
- {t("CycleTimer.WaitingForCycle.lb", "Waiting for program cycle")}
188
+ <span
189
+ style={{
190
+ opacity: showIdlePulsating ? 1 : 0.6,
191
+ transition: "opacity 2s ease-in-out",
192
+ }}
193
+ >
194
+ {t(
195
+ "CycleTimer.WaitingForCycle.lb",
196
+ "Waiting for program cycle",
197
+ )}
198
+ </span>
199
+ <span
200
+ style={{
201
+ display: "inline-block",
202
+ width: "18px",
203
+ textAlign: "left",
204
+ opacity: showIdlePulsating ? 1 : 0.6,
205
+ transition: "opacity 2s ease-in-out",
206
+ }}
207
+ >
208
+ {".".repeat(idleDotsCount)}
209
+ </span>
175
210
  </Typography>
176
211
  </Fade>
177
212
 
@@ -207,15 +242,24 @@ export const DefaultVariant = ({
207
242
  position: "absolute",
208
243
  fontSize: "48px",
209
244
  fontWeight: 500,
210
- color:
211
- currentState === "measured"
212
- ? theme.palette.text.primary
213
- : theme.palette.text.primary,
245
+ color: theme.palette.text.primary,
214
246
  lineHeight: 1,
215
- transition: "color 0.5s ease-out",
247
+ transition: "color 0.8s ease-in-out",
216
248
  }}
217
249
  >
218
- {formatTime(remainingTime)}
250
+ <span
251
+ style={{
252
+ opacity:
253
+ currentState === "measured" && pulsatingFinished
254
+ ? showPulsatingText
255
+ ? 1
256
+ : 0.6
257
+ : 1,
258
+ transition: "opacity 2s ease-in-out",
259
+ }}
260
+ >
261
+ {formatTime(remainingTime)}
262
+ </span>
219
263
  </Typography>
220
264
  </Fade>
221
265
  </Box>
@@ -225,6 +269,7 @@ export const DefaultVariant = ({
225
269
  sx={{
226
270
  height: "16px",
227
271
  display: "flex",
272
+ marginTop: 0.5,
228
273
  alignItems: "center",
229
274
  justifyContent: "center",
230
275
  }}
@@ -245,24 +290,34 @@ export const DefaultVariant = ({
245
290
  fontSize: "12px",
246
291
  color:
247
292
  currentState === "measured"
248
- ? pulsatingFinished
249
- ? theme.palette.text.secondary
250
- : showPulsatingText
251
- ? theme.palette.success.main
252
- : theme.palette.text.secondary
293
+ ? showPulsatingText || pulsatingFinished
294
+ ? theme.palette.success.main
295
+ : theme.palette.text.secondary
253
296
  : theme.palette.text.secondary,
254
297
  transition: "color 0.8s ease-in-out",
255
298
  }}
256
299
  >
257
- {currentState === "measuring"
258
- ? t("CycleTimer.Measuring.lb", "measuring...")
259
- : currentState === "measured"
260
- ? t("CycleTimer.Determined.lb", "determined")
261
- : currentState === "countdown" && maxTime !== null
262
- ? t("CycleTimer.OfTime.lb", {
263
- time: formatTime(maxTime),
264
- })
265
- : ""}
300
+ <span
301
+ style={{
302
+ opacity:
303
+ currentState === "measured" && pulsatingFinished
304
+ ? showPulsatingText
305
+ ? 1
306
+ : 0.6
307
+ : 1,
308
+ transition: "opacity 2s ease-in-out",
309
+ }}
310
+ >
311
+ {currentState === "measuring"
312
+ ? t("CycleTimer.Measuring.lb", "measuring...")
313
+ : currentState === "measured"
314
+ ? t("CycleTimer.Determined.lb", "determined")
315
+ : currentState === "countdown" && maxTime !== null
316
+ ? t("CycleTimer.OfTime.lb", {
317
+ time: formatTime(maxTime),
318
+ })
319
+ : ""}
320
+ </span>
266
321
  </Typography>
267
322
  </Fade>
268
323
  </Box>
@@ -26,6 +26,8 @@ export const SmallVariant = ({
26
26
  showPauseAnimation,
27
27
  showPulsatingText,
28
28
  pulsatingFinished,
29
+ showIdlePulsating,
30
+ idleDotsCount,
29
31
  } = animationState
30
32
 
31
33
  // Simple text-only mode for compact variant in certain states
@@ -103,17 +105,21 @@ export const SmallVariant = ({
103
105
  hasError
104
106
  ? theme.palette.error.light
105
107
  : currentState === "measured"
106
- ? pulsatingFinished
107
- ? theme.palette.text.secondary
108
- : showPulsatingText
109
- ? theme.palette.success.main
110
- : theme.palette.text.secondary
108
+ ? showPulsatingText || pulsatingFinished
109
+ ? theme.palette.success.main
110
+ : theme.palette.text.secondary
111
111
  : theme.palette.success.main
112
112
  }
113
113
  strokeWidth="2"
114
- opacity={0.3}
114
+ opacity={
115
+ currentState === "measured" && pulsatingFinished
116
+ ? showPulsatingText
117
+ ? 1
118
+ : 0.6
119
+ : 0.3
120
+ }
115
121
  style={{
116
- transition: "stroke 0.8s ease-in-out",
122
+ transition: "stroke 0.8s ease-in-out, opacity 2s ease-in-out",
117
123
  }}
118
124
  />
119
125
  {/* Progress ring */}
@@ -126,11 +132,9 @@ export const SmallVariant = ({
126
132
  hasError
127
133
  ? theme.palette.error.light
128
134
  : currentState === "measured"
129
- ? pulsatingFinished
130
- ? theme.palette.text.secondary
131
- : showPulsatingText
132
- ? theme.palette.success.main
133
- : theme.palette.text.secondary
135
+ ? showPulsatingText || pulsatingFinished
136
+ ? theme.palette.success.main
137
+ : theme.palette.text.secondary
134
138
  : theme.palette.success.main
135
139
  }
136
140
  strokeWidth="2"
@@ -138,8 +142,14 @@ export const SmallVariant = ({
138
142
  strokeDasharray={`${2 * Math.PI * 8}`}
139
143
  strokeDashoffset={`${2 * Math.PI * 8 * (1 - (currentState === "idle" ? 0 : timerState.currentProgress) / 100)}`}
140
144
  style={{
145
+ opacity:
146
+ currentState === "measured" && pulsatingFinished
147
+ ? showPulsatingText
148
+ ? 1
149
+ : 0.6
150
+ : 1,
141
151
  transition:
142
- "stroke-dashoffset 0.1s ease-out, stroke 0.8s ease-in-out",
152
+ "stroke-dashoffset 0.1s ease-out, stroke 0.8s ease-in-out, opacity 2s ease-in-out",
143
153
  }}
144
154
  />
145
155
  </svg>
@@ -155,35 +165,65 @@ export const SmallVariant = ({
155
165
  : currentState === "idle"
156
166
  ? "rgba(255, 255, 255, 0.7)"
157
167
  : currentState === "measured"
158
- ? pulsatingFinished
159
- ? theme.palette.text.secondary
160
- : showPulsatingText
161
- ? theme.palette.success.main
162
- : theme.palette.text.secondary
168
+ ? showPulsatingText || pulsatingFinished
169
+ ? theme.palette.success.main
170
+ : theme.palette.text.secondary
163
171
  : theme.palette.text.primary,
164
- fontSize: currentState === "idle" ? "12px" : "14px",
165
- lineHeight: currentState === "idle" ? "166%" : "normal",
166
- letterSpacing: currentState === "idle" ? "0.17px" : "normal",
167
- transition: "color 0.8s ease-in-out, font-size 0.3s ease-out",
172
+ fontSize: "14px",
173
+ lineHeight: "normal",
174
+ letterSpacing: "normal",
175
+ opacity:
176
+ currentState === "idle"
177
+ ? showIdlePulsating
178
+ ? 1
179
+ : 0.6
180
+ : currentState === "measured" && pulsatingFinished
181
+ ? showPulsatingText
182
+ ? 1
183
+ : 0.6
184
+ : 1,
185
+ transition:
186
+ "color 0.8s ease-in-out, font-size 0.3s ease-out, opacity 2s ease-in-out",
168
187
  }}
169
188
  >
170
- {hasError
171
- ? t("CycleTimer.Error.lb", "Error")
172
- : currentState === "idle"
173
- ? t("CycleTimer.WaitingForCycle.lb", "Waiting for program cycle")
174
- : currentState === "measuring"
175
- ? compact
176
- ? `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
177
- : `${formatTime(remainingTime)} / ${t("CycleTimer.Measuring.lb", "measuring...")}`
178
- : currentState === "measured"
179
- ? compact
180
- ? `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
181
- : `${formatTime(remainingTime)} / ${t("CycleTimer.Determined.lb", "determined")}`
182
- : currentState === "countdown" && maxTime !== null
183
- ? compact
184
- ? `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
185
- : `${formatTime(remainingTime)} / ${t("CycleTimer.Time.lb", { time: formatTime(maxTime) })}`
186
- : formatTime(remainingTime)}
189
+ {hasError ? (
190
+ t("CycleTimer.Error.lb", "Error")
191
+ ) : currentState === "idle" ? (
192
+ <>
193
+ <span>
194
+ {t("CycleTimer.WaitingForCycle.lb", "Waiting for program cycle")}
195
+ </span>
196
+ <span
197
+ style={{
198
+ display: "inline-block",
199
+ width: "18px",
200
+ textAlign: "left",
201
+ }}
202
+ >
203
+ {".".repeat(idleDotsCount)}
204
+ </span>
205
+ </>
206
+ ) : currentState === "measuring" ? (
207
+ compact ? (
208
+ `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
209
+ ) : (
210
+ `${formatTime(remainingTime)} / ${t("CycleTimer.Measuring.lb", "measuring...")}`
211
+ )
212
+ ) : currentState === "measured" ? (
213
+ compact ? (
214
+ `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
215
+ ) : (
216
+ `${formatTime(remainingTime)} / ${t("CycleTimer.Determined.lb", "determined")}`
217
+ )
218
+ ) : currentState === "countdown" && maxTime !== null ? (
219
+ compact ? (
220
+ `${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
221
+ ) : (
222
+ `${formatTime(remainingTime)} / ${t("CycleTimer.Time.lb", { time: formatTime(maxTime) })}`
223
+ )
224
+ ) : (
225
+ formatTime(remainingTime)
226
+ )}
187
227
  </Typography>
188
228
  </Box>
189
229
  )
@@ -52,6 +52,8 @@ export const CycleTimer = externalizeComponent(
52
52
  clearErrorAnimation,
53
53
  startPulsatingAnimation,
54
54
  stopPulsatingAnimation,
55
+ startIdleAnimations,
56
+ stopIdleAnimations,
55
57
  triggerFadeTransition,
56
58
  setInitialAnimationState,
57
59
  cleanup,
@@ -82,6 +84,11 @@ export const CycleTimer = externalizeComponent(
82
84
  stopPulsatingAnimation()
83
85
  }
84
86
 
87
+ // Stop idle animations if leaving idle state
88
+ if (prevState === "idle") {
89
+ stopIdleAnimations()
90
+ }
91
+
85
92
  // Trigger fade transition
86
93
  triggerFadeTransition()
87
94
  } else {
@@ -89,10 +96,17 @@ export const CycleTimer = externalizeComponent(
89
96
  setInitialAnimationState()
90
97
  }
91
98
 
99
+ // Start idle animations if entering idle state
100
+ if (timerState.currentState === "idle") {
101
+ startIdleAnimations()
102
+ }
103
+
92
104
  prevStateRef.current = timerState.currentState
93
105
  }, [
94
106
  timerState.currentState,
95
107
  stopPulsatingAnimation,
108
+ stopIdleAnimations,
109
+ startIdleAnimations,
96
110
  triggerFadeTransition,
97
111
  setInitialAnimationState,
98
112
  ])
@@ -55,4 +55,6 @@ export interface AnimationState {
55
55
  pulsatingFinished: boolean
56
56
  showLabels: boolean
57
57
  showMainText: boolean
58
+ showIdlePulsating: boolean
59
+ idleDotsCount: number
58
60
  }
@@ -9,6 +9,8 @@ export const useAnimations = () => {
9
9
  pulsatingFinished: false,
10
10
  showLabels: true,
11
11
  showMainText: true,
12
+ showIdlePulsating: false,
13
+ idleDotsCount: 0,
12
14
  })
13
15
 
14
16
  // Refs for managing timeouts and intervals
@@ -17,6 +19,8 @@ export const useAnimations = () => {
17
19
  const pulsatingIntervalRef = useRef<NodeJS.Timeout | null>(null)
18
20
  const fadeTimeoutRef = useRef<NodeJS.Timeout | null>(null)
19
21
  const pulseCountRef = useRef<number>(0)
22
+ const idlePulsatingIntervalRef = useRef<NodeJS.Timeout | null>(null)
23
+ const idleDotsIntervalRef = useRef<NodeJS.Timeout | null>(null)
20
24
 
21
25
  const triggerPauseAnimation = useCallback(() => {
22
26
  setAnimationState((prev) => ({ ...prev, showPauseAnimation: true }))
@@ -51,36 +55,32 @@ export const useAnimations = () => {
51
55
 
52
56
  const startPulsatingAnimation = useCallback((onComplete?: () => void) => {
53
57
  pulseCountRef.current = 0
58
+ // Start with fade to success color
54
59
  setAnimationState((prev) => ({
55
60
  ...prev,
56
61
  showPulsatingText: true,
57
62
  pulsatingFinished: false,
58
63
  }))
59
64
 
60
- pulsatingIntervalRef.current = setInterval(() => {
61
- pulseCountRef.current += 1
65
+ // After initial success color fade, start slow pulsating like idle
66
+ setTimeout(() => {
67
+ setAnimationState((prev) => ({
68
+ ...prev,
69
+ pulsatingFinished: true, // This will keep the success color and start slow pulsating
70
+ }))
62
71
 
63
- if (pulseCountRef.current >= 8) {
64
- // 4 complete cycles (on->off->on->off = 8 state changes)
65
- if (pulsatingIntervalRef.current) {
66
- clearInterval(pulsatingIntervalRef.current)
67
- pulsatingIntervalRef.current = null
68
- }
69
- setAnimationState((prev) => ({
70
- ...prev,
71
- showPulsatingText: false,
72
- pulsatingFinished: true,
73
- }))
74
- if (onComplete) {
75
- onComplete()
76
- }
77
- } else {
72
+ // Start slow pulsating animation similar to idle
73
+ pulsatingIntervalRef.current = setInterval(() => {
78
74
  setAnimationState((prev) => ({
79
75
  ...prev,
80
76
  showPulsatingText: !prev.showPulsatingText,
81
77
  }))
78
+ }, 2000) // Same slow timing as idle pulsating
79
+
80
+ if (onComplete) {
81
+ onComplete()
82
82
  }
83
- }, 800)
83
+ }, 800) // Initial success color display duration
84
84
  }, [])
85
85
 
86
86
  const stopPulsatingAnimation = useCallback(() => {
@@ -88,6 +88,7 @@ export const useAnimations = () => {
88
88
  clearInterval(pulsatingIntervalRef.current)
89
89
  pulsatingIntervalRef.current = null
90
90
  }
91
+ // Reset all pulsating states to ensure colors are reset
91
92
  setAnimationState((prev) => ({
92
93
  ...prev,
93
94
  showPulsatingText: false,
@@ -96,6 +97,45 @@ export const useAnimations = () => {
96
97
  pulseCountRef.current = 0
97
98
  }, [])
98
99
 
100
+ const startIdleAnimations = useCallback(() => {
101
+ // Start pulsating animation for the text
102
+ setAnimationState((prev) => ({
103
+ ...prev,
104
+ showIdlePulsating: true,
105
+ }))
106
+
107
+ idlePulsatingIntervalRef.current = setInterval(() => {
108
+ setAnimationState((prev) => ({
109
+ ...prev,
110
+ showIdlePulsating: !prev.showIdlePulsating,
111
+ }))
112
+ }, 2000) // Slower pulsate every 2 seconds
113
+
114
+ // Start animated dots
115
+ idleDotsIntervalRef.current = setInterval(() => {
116
+ setAnimationState((prev) => ({
117
+ ...prev,
118
+ idleDotsCount: (prev.idleDotsCount + 1) % 4, // Cycle through 0, 1, 2, 3
119
+ }))
120
+ }, 800) // Change dots every 800ms
121
+ }, [])
122
+
123
+ const stopIdleAnimations = useCallback(() => {
124
+ if (idlePulsatingIntervalRef.current) {
125
+ clearInterval(idlePulsatingIntervalRef.current)
126
+ idlePulsatingIntervalRef.current = null
127
+ }
128
+ if (idleDotsIntervalRef.current) {
129
+ clearInterval(idleDotsIntervalRef.current)
130
+ idleDotsIntervalRef.current = null
131
+ }
132
+ setAnimationState((prev) => ({
133
+ ...prev,
134
+ showIdlePulsating: false,
135
+ idleDotsCount: 0,
136
+ }))
137
+ }, [])
138
+
99
139
  const triggerFadeTransition = useCallback(() => {
100
140
  setAnimationState((prev) => ({
101
141
  ...prev,
@@ -138,6 +178,12 @@ export const useAnimations = () => {
138
178
  if (pulsatingIntervalRef.current) {
139
179
  clearInterval(pulsatingIntervalRef.current)
140
180
  }
181
+ if (idlePulsatingIntervalRef.current) {
182
+ clearInterval(idlePulsatingIntervalRef.current)
183
+ }
184
+ if (idleDotsIntervalRef.current) {
185
+ clearInterval(idleDotsIntervalRef.current)
186
+ }
141
187
  }, [])
142
188
 
143
189
  return {
@@ -147,6 +193,8 @@ export const useAnimations = () => {
147
193
  clearErrorAnimation,
148
194
  startPulsatingAnimation,
149
195
  stopPulsatingAnimation,
196
+ startIdleAnimations,
197
+ stopIdleAnimations,
150
198
  triggerFadeTransition,
151
199
  setInitialAnimationState,
152
200
  cleanup,
@@ -56,25 +56,27 @@ export const useTimerLogic = ({
56
56
  remainingTime: 0,
57
57
  isRunning: false,
58
58
  isPausedState: false,
59
+ currentProgress: 0, // Immediately reset progress to 0
59
60
  }))
60
61
  pausedTimeRef.current = 0
61
62
  startTimeRef.current = null
62
- progressInterpolator.setTarget([0])
63
+ progressInterpolator.setImmediate([0]) // Use setImmediate for instant reset
63
64
  }, [progressInterpolator])
64
65
 
65
66
  const startMeasuring = useCallback(
66
67
  (elapsedSeconds: number = 0) => {
68
+ const initialProgress = ((elapsedSeconds / 60) % 1) * 100
67
69
  setTimerState((prev) => ({
68
70
  ...prev,
69
71
  currentState: "measuring",
70
72
  maxTime: null,
71
73
  remainingTime: elapsedSeconds,
72
74
  isPausedState: false,
75
+ currentProgress: initialProgress, // Immediately set progress
73
76
  }))
74
77
  pausedTimeRef.current = 0
75
78
 
76
- const initialProgress = ((elapsedSeconds / 60) % 1) * 100
77
- progressInterpolator.setTarget([initialProgress])
79
+ progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
78
80
 
79
81
  if (autoStart) {
80
82
  startTimeRef.current = Date.now() - elapsedSeconds * 1000
@@ -88,17 +90,18 @@ export const useTimerLogic = ({
88
90
 
89
91
  const startCountUp = useCallback(
90
92
  (elapsedSeconds: number = 0) => {
93
+ const initialProgress = ((elapsedSeconds / 60) % 1) * 100
91
94
  setTimerState((prev) => ({
92
95
  ...prev,
93
96
  currentState: "countup",
94
97
  maxTime: null,
95
98
  remainingTime: elapsedSeconds,
96
99
  isPausedState: false,
100
+ currentProgress: initialProgress, // Immediately set progress
97
101
  }))
98
102
  pausedTimeRef.current = 0
99
103
 
100
- const initialProgress = ((elapsedSeconds / 60) % 1) * 100
101
- progressInterpolator.setTarget([initialProgress])
104
+ progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
102
105
 
103
106
  if (autoStart) {
104
107
  startTimeRef.current = Date.now() - elapsedSeconds * 1000
@@ -128,13 +131,16 @@ export const useTimerLogic = ({
128
131
  if (maxTimeSeconds !== undefined) {
129
132
  // Count-down mode
130
133
  const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
131
- setTimerState((prev) => ({ ...prev, remainingTime: remainingSeconds }))
132
-
133
134
  const initialProgress =
134
135
  elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
135
136
 
136
- progressInterpolator.setTarget([initialProgress])
137
- progressInterpolator.update(1)
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
138
144
 
139
145
  if (remainingSeconds === 0) {
140
146
  setTimerState((prev) => ({ ...prev, isRunning: false }))
@@ -152,11 +158,14 @@ export const useTimerLogic = ({
152
158
  }
153
159
  } else {
154
160
  // Count-up mode
155
- setTimerState((prev) => ({ ...prev, remainingTime: elapsedSeconds }))
156
-
157
161
  const initialProgress = ((elapsedSeconds / 60) % 1) * 100
158
- progressInterpolator.setTarget([initialProgress])
159
- progressInterpolator.update(1)
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
160
169
 
161
170
  if (autoStart) {
162
171
  setTimeout(() => {