fix(sidebar): keep session rename input visible while editing (#781)

The rename input shares a parent div that uses `group-hover:opacity-100`,
so moving the cursor off the row visually hid the input mid-edit.

While editing, force the action panel to `opacity-100` and dismiss it
via an outside-click listener instead of mouseleave. Also hide the
relative-time badge so it does not overlap the input.
This commit is contained in:
Alex Navarro
2026-05-29 09:04:35 -06:00
committed by GitHub
parent 27e509a9b8
commit 951f58751c

View File

@@ -1,3 +1,4 @@
import { useEffect, useRef } from 'react';
import { Check, Edit2, Trash2, X } from 'lucide-react'; import { Check, Edit2, Trash2, X } from 'lucide-react';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
@@ -76,7 +77,28 @@ export default function SidebarSessionItem({
}: SidebarSessionItemProps) { }: SidebarSessionItemProps) {
const sessionView = createSessionViewModel(session, currentTime, t); const sessionView = createSessionViewModel(session, currentTime, t);
const isSelected = selectedSession?.id === session.id; const isSelected = selectedSession?.id === session.id;
const isEditing = editingSession === session.id;
const compactSessionAge = formatCompactSessionAge(sessionView.sessionTime, currentTime); const compactSessionAge = formatCompactSessionAge(sessionView.sessionTime, currentTime);
const editingContainerRef = useRef<HTMLDivElement>(null);
// The rename panel sits inside a group-hover opacity wrapper, so leaving the row
// would visually hide it. While editing, dismiss only when the user clicks outside
// the panel (matches Escape / cancel-button behaviour).
useEffect(() => {
if (!isEditing) {
return;
}
const handlePointerDown = (event: MouseEvent) => {
const container = editingContainerRef.current;
if (container && !container.contains(event.target as Node)) {
onCancelEditingSession();
}
};
document.addEventListener('mousedown', handlePointerDown);
return () => document.removeEventListener('mousedown', handlePointerDown);
}, [isEditing, onCancelEditingSession]);
// Sessions are owned by a project identified by `projectId` (DB primary key) // Sessions are owned by a project identified by `projectId` (DB primary key)
// after the projectName → projectId migration. // after the projectName → projectId migration.
@@ -174,7 +196,12 @@ export default function SidebarSessionItem({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="truncate text-xs font-medium text-foreground">{sessionView.sessionName}</div> <div className="truncate text-xs font-medium text-foreground">{sessionView.sessionName}</div>
{compactSessionAge && ( {compactSessionAge && (
<span className="ml-auto flex-shrink-0 text-[11px] text-muted-foreground transition-opacity duration-200 group-hover:opacity-0"> <span
className={cn(
'ml-auto flex-shrink-0 text-[11px] text-muted-foreground transition-opacity duration-200',
isEditing ? 'opacity-0' : 'group-hover:opacity-0',
)}
>
{compactSessionAge} {compactSessionAge}
</span> </span>
)} )}
@@ -186,8 +213,14 @@ export default function SidebarSessionItem({
</div> </div>
</Button> </Button>
<div className="absolute right-2 top-1/2 flex -translate-y-1/2 transform items-center gap-1 opacity-0 transition-all duration-200 group-hover:opacity-100"> <div
{editingSession === session.id ? ( ref={editingContainerRef}
className={cn(
'absolute right-2 top-1/2 flex -translate-y-1/2 transform items-center gap-1 transition-all duration-200',
isEditing ? 'opacity-100' : 'opacity-0 group-hover:opacity-100',
)}
>
{isEditing ? (
<> <>
<input <input
type="text" type="text"