Skip to content

Commit 1f50188

Browse files
authored
ENG-449/Checkpoint UI hover debounce (RooCodeInc#2806)
* initial * added debounce for checkmark expanded ui * corrected bookmark size * removed unnecesary cleanup * fixed removed code * removed cleanup for real this time * refactored to reduce complexity, added cleanup
1 parent d001034 commit 1f50188

File tree

2 files changed

+40
-21
lines changed

2 files changed

+40
-21
lines changed

.changeset/gorgeous-bees-talk.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Added debounce for checkmark expanded ui

webview-ui/src/components/common/CheckmarkControl.tsx

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ export const CheckmarkControl = ({ messageTs, isCheckpointCheckedOut, isLastRow
2525
const tooltipRef = useRef<HTMLDivElement>(null)
2626
const [isComponentHovered, setIsComponentHovered] = useState(false)
2727
const [isLineHovered, setIsLineHovered] = useState(false)
28+
const hideTimerRef = useRef<NodeJS.Timeout | null>(null)
29+
30+
// Cleanup timer on unmount
31+
useEffect(() => {
32+
return () => {
33+
if (hideTimerRef.current) {
34+
clearTimeout(hideTimerRef.current)
35+
}
36+
}
37+
}, [])
2838

2939
const { refs, floatingStyles, update, placement } = useFloating({
3040
placement: "bottom-end",
@@ -159,6 +169,17 @@ export const CheckmarkControl = ({ messageTs, isCheckpointCheckedOut, isLastRow
159169
}
160170
}
161171

172+
const handleDebounceMouseLeave = (additionalCheck?: () => void) => {
173+
if (hideTimerRef.current) {
174+
clearTimeout(hideTimerRef.current)
175+
}
176+
hideTimerRef.current = setTimeout(() => {
177+
setIsLineHovered(false)
178+
setIsComponentHovered(false)
179+
}, checkpointHoverDebounce)
180+
additionalCheck?.()
181+
}
182+
162183
const handleControlsMouseLeave = (e: React.MouseEvent) => {
163184
const tooltipElement = tooltipRef.current
164185

@@ -190,43 +211,36 @@ export const CheckmarkControl = ({ messageTs, isCheckpointCheckedOut, isLastRow
190211
// The line should still be highlighted when the checkpoint is checked out
191212
const shouldShowHoveredLine = isCheckpointCheckedOut || isLineHovered || isComponentHovered || showRestoreConfirm
192213

214+
// Debounce time for hiding the ExpandedUI
215+
const checkpointHoverDebounce: number = 400
216+
193217
return (
194218
<Container isMenuOpen={showRestoreConfirm} $isCheckedOut={isCheckpointCheckedOut}>
195219
{/* Line indicator is still styled differently for checked out checkpoints */}
196220
<CheckpointIndicator
197221
$isCheckedOut={isCheckpointCheckedOut}
198222
$isHovered={shouldShowHoveredLine}
199223
onMouseEnter={() => setIsLineHovered(true)}
200-
onMouseLeave={() => {
201-
setTimeout(() => {
202-
if (!isComponentHovered && !showRestoreConfirm) {
203-
setIsLineHovered(false)
204-
}
205-
}, 50)
206-
}}
224+
onMouseLeave={() => handleDebounceMouseLeave()}
207225
/>
208226

209-
<HoverArea
210-
onMouseEnter={() => setIsLineHovered(true)}
211-
onMouseLeave={() => {
212-
if (!isComponentHovered && !showRestoreConfirm) {
213-
setIsLineHovered(false)
214-
}
215-
}}
216-
/>
227+
<HoverArea onMouseEnter={() => setIsLineHovered(true)} onMouseLeave={() => handleDebounceMouseLeave()} />
217228

218229
{showExpandedUI && (
219230
<ExpandedUI
220231
$isCheckedOut={isCheckpointCheckedOut}
221232
$isLastRow={isLastRow}
222233
onMouseEnter={() => {
234+
if (hideTimerRef.current) {
235+
clearTimeout(hideTimerRef.current)
236+
hideTimerRef.current = null
237+
}
223238
setIsComponentHovered(true)
224239
setIsLineHovered(true)
225240
}}
226241
onMouseLeave={(e) => {
227242
if (!showRestoreConfirm) {
228-
setIsComponentHovered(false)
229-
setIsLineHovered(false)
243+
handleDebounceMouseLeave()
230244
} else {
231245
handleControlsMouseLeave(e)
232246
}
@@ -370,17 +384,17 @@ const CheckpointIndicator = styled.div<{
370384
top: 0;
371385
/* Make checked out checkpoints have a more visible line */
372386
width: ${(props) =>
373-
props.$isCheckedOut ? "10px" /* Wider default for checked out checkpoints */ : props.$isHovered ? "12px" : "8px"};
374-
height: 3px;
387+
props.$isCheckedOut ? "10px" /* Wider default for checked out checkpoints */ : props.$isHovered ? "13px" : "10px"};
388+
height: 6px;
375389
background-color: ${(props) =>
376390
props.$isCheckedOut ? "var(--vscode-textLink-foreground)" : "var(--vscode-descriptionForeground)"};
377391
opacity: ${(props) => (props.$isCheckedOut ? 1 /* Always full opacity for checked out */ : props.$isHovered ? 1 : 0.6)};
378392
transition:
379393
opacity 0.15s ease-in-out,
380394
width 0.15s ease-in-out;
381395
cursor: pointer;
382-
border-top-right-radius: 2px;
383-
border-bottom-right-radius: 2px;
396+
border-top-right-radius: 3px;
397+
border-bottom-right-radius: 3px;
384398
z-index: 5;
385399
`
386400

0 commit comments

Comments
 (0)