🪤 refactor: Reset Interaction State When Mouse Leaves Conversation Item (#11402)

* fix: reset hasInteracted state when mouse leaves conversation item

* fix lint

* refactor: update state setter types in ConvoOptions and DeleteButton components for Fixing types issue

* fix: Add blur handler and focus-aware popover close for Conversation a11y
This commit is contained in:
mohamed magdy 2026-01-26 15:07:27 +00:00 committed by GitHub
parent 0b4deac953
commit 5310529ee0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 4 deletions

View file

@ -47,6 +47,7 @@ export default function Conversation({
const [hasInteracted, setHasInteracted] = useState(false);
const previousTitle = useRef(title);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (title !== previousTitle.current) {
@ -109,6 +110,37 @@ export default function Conversation({
}
}, [hasInteracted]);
const handleMouseLeave = useCallback(() => {
if (!isPopoverActive) {
setHasInteracted(false);
}
}, [isPopoverActive]);
const handleBlur = useCallback(
(e: React.FocusEvent<HTMLDivElement>) => {
// Don't reset if focus is moving to a child element within this container
if (e.currentTarget.contains(e.relatedTarget as Node)) {
return;
}
if (!isPopoverActive) {
setHasInteracted(false);
}
},
[isPopoverActive],
);
const handlePopoverOpenChange = useCallback((open: boolean) => {
setIsPopoverActive(open);
if (!open) {
requestAnimationFrame(() => {
const container = containerRef.current;
if (container && !container.contains(document.activeElement)) {
setHasInteracted(false);
}
});
}
}, []);
const handleNavigation = (ctrlOrMetaKey: boolean) => {
if (ctrlOrMetaKey) {
toggleNav();
@ -141,12 +173,13 @@ export default function Conversation({
isActiveConvo,
conversationId,
isPopoverActive,
setIsPopoverActive,
setIsPopoverActive: handlePopoverOpenChange,
isShiftHeld: isActiveConvo ? isShiftHeld : false,
};
return (
<div
ref={containerRef}
className={cn(
'group relative flex h-12 w-full items-center rounded-lg outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-black dark:focus-visible:ring-white md:h-9',
isActiveConvo || isPopoverActive
@ -159,7 +192,9 @@ export default function Conversation({
title: title || localize('com_ui_untitled'),
})}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onFocus={handleMouseEnter}
onBlur={handleBlur}
onClick={(e) => {
if (renaming) {
return;

View file

@ -35,7 +35,7 @@ function ConvoOptions({
retainView: () => void;
renameHandler: (e: MouseEvent) => void;
isPopoverActive: boolean;
setIsPopoverActive: React.Dispatch<React.SetStateAction<boolean>>;
setIsPopoverActive: (open: boolean) => void;
isActiveConvo: boolean;
isShiftHeld?: boolean;
}) {

View file

@ -25,7 +25,7 @@ type DeleteButtonProps = {
showDeleteDialog?: boolean;
setShowDeleteDialog?: (value: boolean) => void;
triggerRef?: React.RefObject<HTMLButtonElement>;
setMenuOpen?: React.Dispatch<React.SetStateAction<boolean>>;
setMenuOpen?: (open: boolean) => void;
};
export function DeleteConversationDialog({
@ -35,7 +35,7 @@ export function DeleteConversationDialog({
retainView,
title,
}: {
setMenuOpen?: React.Dispatch<React.SetStateAction<boolean>>;
setMenuOpen?: (open: boolean) => void;
setShowDeleteDialog: (value: boolean) => void;
conversationId: string;
retainView: () => void;