import { useRef, useState } from 'react'; import { highlightMentions } from '../mentions'; import { PopoverPortal } from './popover-portal'; import type { Attachment } from '../types'; import type { UiMessage } from '../hooks/use-messages'; const REACTION_EMOJIS = ['👍', '❤️', '😂', '🎉', '👀']; const isImage = (mime: string): boolean => mime.startsWith('image/'); function AttachmentView({ att }: { att: Attachment }) { if (isImage(att.mime)) { return ( {att.name} ); } return ( 📎 {att.name} ); } /** One rendered message: bubble (with @mention highlighting), attachment, reactions, seen tick, * and — in the main thread only — a thread/reply affordance. */ export function MessageItem({ message, memberNames, canReact, onReact, seen, replyCount, onOpenThread, }: { message: UiMessage; memberNames: string[]; canReact: boolean; onReact: (messageId: string, emoji: string) => void; seen: boolean; /** Present only in the main thread (not inside the pane). undefined => no thread affordance. */ replyCount?: number; onOpenThread?: (messageId: string) => void; }) { const [pickerOpen, setPickerOpen] = useState(false); const reactBtnRef = useRef(null); const m = message; return (
{m.text ? highlightMentions(m.text, memberNames) : null} {m.attachment ? : null}
{canReact ? (
{pickerOpen ? ( setPickerOpen(false)}>
{REACTION_EMOJIS.map((e) => ( ))}
) : null}
) : null} {onOpenThread ? ( ) : null}
{m.reactions && m.reactions.length > 0 ? (
{m.reactions.map((r) => ( ))}
) : null} {replyCount !== undefined && replyCount > 0 && onOpenThread ? ( ) : null} {message.mine && seen ? Seen : null}
); }