import React, { useMemo, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; import type { MemberWithUser } from '@backspace/shared'; import { Avatar } from '../ui/Avatar'; import { useSpaceStore } from '../../stores/spaceStore'; import { useFloatingPosition } from '../../hooks/useFloatingPosition'; const MAX_RESULTS = 8; interface MentionPopoverProps { query: string; selectedIndex: number; onSelect: (member: MemberWithUser) => void; anchorRef: React.RefObject; } export function MentionPopover({ query, selectedIndex, onSelect, anchorRef }: MentionPopoverProps) { const members = useSpaceStore((s) => s.members); const spaces = useSpaceStore((s) => s.spaces); const currentSpaceId = useSpaceStore((s) => s.currentSpaceId); const selectedRef = useRef(null); const floatingRef = useRef(null); const ownerId = spaces.find((s) => s.id === currentSpaceId)?.ownerId; const filtered = useMemo(() => { const q = query.toLowerCase(); return members .filter((m) => { const name = (m.user.displayName ?? m.user.username).toLowerCase(); const username = m.user.username.toLowerCase(); return name.includes(q) || username.includes(q); }) .slice(0, MAX_RESULTS); }, [members, query]); const { style } = useFloatingPosition(anchorRef, floatingRef, { placement: 'top', offset: 4, enabled: filtered.length > 0, }); // Scroll selected item into view useEffect(() => { selectedRef.current?.scrollIntoView({ block: 'nearest' }); }, [selectedIndex]); if (filtered.length === 0) return null; const getMemberColor = (member: MemberWithUser): string | undefined => { if (member.roles && member.roles.length > 0) { const sorted = [...member.roles].sort((a, b) => b.position - a.position); return sorted[0]!.color; } if (ownerId && member.userId === ownerId) return '#fda4af'; return undefined; }; return createPortal(
Members
{filtered.map((member, i) => { const displayName = member.user.displayName ?? member.user.username; const roleColor = getMemberColor(member); const isSelected = i === selectedIndex; return (
onSelect(member)} className={`flex items-center gap-2.5 px-2 py-1.5 mx-1 rounded cursor-pointer transition-colors ${ isSelected ? 'bg-interactive-selected' : 'hover:bg-interactive-hover' }`} > {displayName} {member.user.displayName && ( @{member.user.username} )}
); })}
, document.body, ); }