From b7dcb1a5152633f084b76216ec5ecf1845d8e2a6 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 13 Apr 2026 20:07:09 -1000 Subject: [PATCH] refactor: replace CSS group-hover with Radix HoverCard for message actions Swap the fragile CSS group-hover opacity pattern for Radix HoverCard which handles hover intent, open/close delays, and portal rendering. Actions appear below the bubble on hover via stripped HoverCardContent (no border, shadow, padding, or background). Removes group class. --- .../src/features/chat/ui/MessageBubble.tsx | 158 ++++++++++-------- 1 file changed, 86 insertions(+), 72 deletions(-) diff --git a/ui/goose2/src/features/chat/ui/MessageBubble.tsx b/ui/goose2/src/features/chat/ui/MessageBubble.tsx index b594731867..371bd18429 100644 --- a/ui/goose2/src/features/chat/ui/MessageBubble.tsx +++ b/ui/goose2/src/features/chat/ui/MessageBubble.tsx @@ -30,6 +30,11 @@ import { ReasoningContent, } from "@/shared/ui/ai-elements/reasoning"; import { Button } from "@/shared/ui/button"; +import { + HoverCard, + HoverCardTrigger, + HoverCardContent, +} from "@/shared/ui/hover-card"; import { ToolChainCards, type ToolChainItem } from "./ToolChainCards"; import { ClickableImage } from "./ClickableImage"; import { useArtifactLinkHandler } from "@/features/chat/hooks/useArtifactLinkHandler"; @@ -414,7 +419,7 @@ export const MessageBubble = memo(function MessageBubble({ return (
) : ( - <> + {/* biome-ignore lint/a11y/useKeyWithClickEvents: delegated link handler */} {/* biome-ignore lint/a11y/noStaticElementInteractions: delegated link handler */} -
- {messageAttachments.length > 0 && ( -
- {messageAttachments.map((attachment) => ( - - ))} -
- )} - {groupContentSections(content).map((section, sectionIdx) => { - if (section.type === "toolChain") { - const toolItems = section.items as ToolChainItem[]; - return ; - } - const block = section.items[0] as MessageContent; - return ( -
- {renderContentBlock( - block, - sectionIdx, - { - defaultImageAlt: t("message.defaultImageAlt"), - redactedThinking: t("message.redactedThinking"), - }, - isStreaming, - isUser, - )} + +
+ {messageAttachments.length > 0 && ( +
+ {messageAttachments.map((attachment) => ( + + ))}
- ); - })} - {pathNotice && ( -

- {pathNotice} -

- )} -
- - {/* Hover actions + timestamp */} - - {textContent && } - {onRetryMessage && ( - onRetryMessage(message.id)} - > - - - )} - {isUser && onEditMessage && !isEditing && ( - onEditMessage(message.id)} - > - - - )} - - {formatDate(created, { - hour: "2-digit", - minute: "2-digit", + )} + {groupContentSections(content).map((section, sectionIdx) => { + if (section.type === "toolChain") { + const toolItems = section.items as ToolChainItem[]; + return ; + } + const block = section.items[0] as MessageContent; + return ( +
+ {renderContentBlock( + block, + sectionIdx, + { + defaultImageAlt: t("message.defaultImageAlt"), + redactedThinking: t("message.redactedThinking"), + }, + isStreaming, + isUser, + )} +
+ ); })} -
-
- + {pathNotice && ( +

+ {pathNotice} +

+ )} +
+ + + {/* Hover actions + timestamp — rendered in portal, stripped styling */} + + + {textContent && } + {onRetryMessage && ( + onRetryMessage(message.id)} + > + + + )} + {isUser && onEditMessage && ( + onEditMessage(message.id)} + > + + + )} + + {formatDate(created, { + hour: "2-digit", + minute: "2-digit", + })} + + + + )}