From ef1adb663dcb9a644782f07f9f2ff89d1344da8e Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Fri, 17 Apr 2026 21:50:50 +0800 Subject: [PATCH] fix(room): replace manual emoji picker positioning with Popover Manual getBoundingClientRect positioning caused the picker to appear at the far right of the room and shift content. Replaced with shadcn Popover which handles anchor positioning, flipping, and portal rendering automatically. --- src/components/room/RoomMessageBubble.tsx | 81 +++++++++-------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/src/components/room/RoomMessageBubble.tsx b/src/components/room/RoomMessageBubble.tsx index 9f64c7a..cd8736c 100644 --- a/src/components/room/RoomMessageBubble.tsx +++ b/src/components/room/RoomMessageBubble.tsx @@ -6,9 +6,10 @@ import { parseFunctionCalls, type FunctionCall } from '@/lib/functionCallParser' import { cn } from '@/lib/utils'; import { AlertCircle, AlertTriangle, ChevronDown, ChevronUp, Copy, Edit2, Reply as ReplyIcon, Trash2, History, MoreHorizontal, MessageSquare } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { SmilePlus } from 'lucide-react'; import { useUser, useRoom } from '@/contexts'; -import { memo, useMemo, useState, useCallback, useRef } from 'react'; +import { memo, useMemo, useCallback, useRef } from 'react'; import { toast } from 'sonner'; import { ModelIcon } from './icon-match'; import { FunctionCallBadge } from './FunctionCallBadge'; @@ -79,10 +80,7 @@ export const RoomMessageBubble = memo(function RoomMessageBubble({ const [isEditing, setIsEditing] = useState(false); const [editContent, setEditContent] = useState(message.content); const [isSavingEdit, setIsSavingEdit] = useState(false); - const [showReactionPicker, setShowReactionPicker] = useState(false); const containerRef = useRef(null); - const reactionButtonRef = useRef(null); - const [reactionPickerPosition, setReactionPickerPosition] = useState<{ top: number; left: number } | null>(null); const isAi = ['ai', 'system', 'tool'].includes(message.sender_type); const isSystem = message.sender_type === 'system'; @@ -121,20 +119,8 @@ export const RoomMessageBubble = memo(function RoomMessageBubble({ } catch (err) { console.warn('[RoomMessage] Failed to update reaction:', err); } - setShowReactionPicker(false); }, [roomId, message.id, message.reactions, wsClient]); - const handleOpenReactionPicker = useCallback(() => { - if (reactionButtonRef.current) { - const rect = reactionButtonRef.current.getBoundingClientRect(); - setReactionPickerPosition({ - top: rect.bottom + 8, // 8px below the button - left: rect.left + rect.width / 2, - }); - } - setShowReactionPicker(true); - }, []); - const functionCalls = useMemo( () => message.content_type === 'text' || message.content_type === 'Text' @@ -408,16 +394,37 @@ export const RoomMessageBubble = memo(function RoomMessageBubble({ {!isEditing && !isRevoked && !isPending && (
{/* Add reaction */} - + + + + + } + /> + +

Select emoji

+
+ {COMMON_EMOJIS.map((emoji) => ( + + ))} +
+
+
{/* Reply */} {onReply && (