From 8b6530c9013c4d0ccc49c4c1967606731ae79af3 Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Wed, 11 Mar 2026 23:29:37 +0800 Subject: [PATCH] Fix hover jitter by overlaying actions instead of swapping content The type pill, time label, and priority pill were being removed on hover and replaced with action buttons, causing layout reflow and visible jitter. Now the labels stay rendered (invisible when hovered for todos/reminders) to hold their space, and action buttons are absolutely positioned on top. Events show no actions so their labels stay visible on hover. Zero layout shift. Co-Authored-By: Claude Opus 4.6 --- .../components/dashboard/UpcomingWidget.tsx | 169 +++++++++--------- 1 file changed, 84 insertions(+), 85 deletions(-) diff --git a/frontend/src/components/dashboard/UpcomingWidget.tsx b/frontend/src/components/dashboard/UpcomingWidget.tsx index 7a18533..2d821ea 100644 --- a/frontend/src/components/dashboard/UpcomingWidget.tsx +++ b/frontend/src/components/dashboard/UpcomingWidget.tsx @@ -299,100 +299,99 @@ export default function UpcomingWidget({ items }: UpcomingWidgetProps) { )} - {/* Inline quick actions (desktop hover) */} - {isHovered && item.type === 'todo' && ( - - )} + {/* Right side: static info + action overlay */} +
+ {/* Always-rendered labels (stable layout) */} +
+ {timeLabel && ( + + {timeLabel} + + )} + + {item.priority && item.priority !== 'none' && ( + + )} +
- {isHovered && item.type === 'reminder' && ( -
- {/* Snooze button with dropdown */} -
+ {/* Action buttons overlaid in same space */} + {isHovered && item.type === 'todo' && ( +
- {snoozeOpen === itemKey && ( -
- - - -
- )}
- {/* Dismiss button */} - -
- )} + )} - {/* Time label */} - {timeLabel && !isHovered && ( - - {timeLabel} - - )} - - {/* Type pill */} - {!isHovered && ( - - )} - - {/* Priority pill (todos only) */} - {!isHovered && item.priority && item.priority !== 'none' && ( - - )} + {isHovered && item.type === 'reminder' && ( +
+
+ + {snoozeOpen === itemKey && ( +
+ + + +
+ )} +
+ +
+ )} +
); })}