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 <noreply@anthropic.com>
This commit is contained in:
parent
66e230f740
commit
8b6530c901
@ -299,23 +299,52 @@ export default function UpcomingWidget({ items }: UpcomingWidgetProps) {
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Inline quick actions (desktop hover) */}
|
||||
{/* Right side: static info + action overlay */}
|
||||
<div className="relative flex items-center gap-1.5 shrink-0">
|
||||
{/* Always-rendered labels (stable layout) */}
|
||||
<div className={cn('flex items-center gap-1.5', isHovered && (item.type === 'todo' || item.type === 'reminder') && 'invisible')}>
|
||||
{timeLabel && (
|
||||
<span className="text-xs text-muted-foreground shrink-0 whitespace-nowrap tabular-nums">
|
||||
{timeLabel}
|
||||
</span>
|
||||
)}
|
||||
<span className={cn(
|
||||
'text-[9px] px-1.5 py-0.5 rounded font-medium shrink-0 hidden sm:inline-block',
|
||||
config.pillBg,
|
||||
config.pillText
|
||||
)}>
|
||||
{config.label}
|
||||
</span>
|
||||
{item.priority && item.priority !== 'none' && (
|
||||
<span className={cn(
|
||||
'text-[9px] font-semibold px-1.5 py-0.5 rounded shrink-0 hidden sm:inline-block',
|
||||
item.priority === 'high' ? 'bg-red-500/10 text-red-400' :
|
||||
item.priority === 'medium' ? 'bg-yellow-500/10 text-yellow-400' :
|
||||
'bg-green-500/10 text-green-400'
|
||||
)}>
|
||||
{item.priority}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action buttons overlaid in same space */}
|
||||
{isHovered && item.type === 'todo' && (
|
||||
<div className="absolute inset-0 flex items-center justify-end">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleTodo.mutate(item.id);
|
||||
}}
|
||||
className="p-1 rounded hover:bg-green-500/15 text-muted-foreground hover:text-green-400 transition-colors shrink-0"
|
||||
className="p-1 rounded hover:bg-green-500/15 text-muted-foreground hover:text-green-400 transition-colors"
|
||||
title="Complete todo"
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isHovered && item.type === 'reminder' && (
|
||||
<div className="flex items-center gap-0.5 shrink-0">
|
||||
{/* Snooze button with dropdown */}
|
||||
<div className="absolute inset-0 flex items-center justify-end gap-0.5">
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@ -350,7 +379,6 @@ export default function UpcomingWidget({ items }: UpcomingWidgetProps) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Dismiss button */}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@ -363,36 +391,7 @@ export default function UpcomingWidget({ items }: UpcomingWidgetProps) {
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Time label */}
|
||||
{timeLabel && !isHovered && (
|
||||
<span className="text-xs text-muted-foreground shrink-0 whitespace-nowrap tabular-nums">
|
||||
{timeLabel}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Type pill */}
|
||||
{!isHovered && (
|
||||
<span className={cn(
|
||||
'text-[9px] px-1.5 py-0.5 rounded font-medium shrink-0 hidden sm:block',
|
||||
config.pillBg,
|
||||
config.pillText
|
||||
)}>
|
||||
{config.label}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Priority pill (todos only) */}
|
||||
{!isHovered && item.priority && item.priority !== 'none' && (
|
||||
<span className={cn(
|
||||
'text-[9px] font-semibold px-1.5 py-0.5 rounded shrink-0 hidden sm:block',
|
||||
item.priority === 'high' ? 'bg-red-500/10 text-red-400' :
|
||||
item.priority === 'medium' ? 'bg-yellow-500/10 text-yellow-400' :
|
||||
'bg-green-500/10 text-green-400'
|
||||
)}>
|
||||
{item.priority}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user