import type { DmChannel, User } from '@backspace/shared'; import { Avatar } from '../ui/Avatar'; import { Tooltip } from '../ui/Tooltip'; import { parseFederatedUsername, isSelf } from '../../utils/identity'; import { formatDmTimestamp, formatDmPreview } from '../../utils/dmFormatters'; import { getRejectedPeerOrigins } from '../../hooks/useWebSocket'; function isMemberUnreachable(homeInstance: string | null | undefined): boolean { if (!homeInstance) return false; const normalized = homeInstance.startsWith('http') ? homeInstance : `https://${homeInstance}`; return getRejectedPeerOrigins().has(normalized); } interface DmListItemProps { dm: DmChannel; isActive: boolean; isUnread: boolean; user: User; onSelect: (id: string) => void; onClose: (id: string) => void; onLeave: (id: string) => void; onContextMenu?: (e: React.MouseEvent, id: string) => void; } export function DmListItem({ dm, isActive, isUnread, user, onSelect, onClose, onLeave, onContextMenu }: DmListItemProps) { const otherMembers = dm.members.filter(m => !isSelf(m, user)); const isGroup = !!dm.ownerId; if (otherMembers.length === 0 && !isGroup) return null; const firstOther = isGroup ? null : otherMembers[0]; const { baseName, domain } = parseFederatedUsername(firstOther?.username ?? ''); const displayName = isGroup ? (otherMembers.length > 0 ? otherMembers.map(m => m.displayName ?? parseFederatedUsername(m.username).baseName).join(', ') : 'Empty Group') : firstOther?.displayName ?? baseName; const handleClick = () => onSelect(dm.id); const handleClose = (e: React.MouseEvent) => { e.stopPropagation(); if (isGroup) { onLeave(dm.id); } else { onClose(dm.id); } }; const handleContextMenu = onContextMenu ? (e: React.MouseEvent) => onContextMenu(e, dm.id) : undefined; // ── State-driven classes ────────────────────────────────────────────── // Container: 6px radius (up from 4px), 44px height (up from 42px) const containerClass = `relative flex items-center gap-3 px-2 h-[44px] rounded-[6px] cursor-pointer transition-colors group ${ isActive ? 'bg-interactive-selected text-white' : isUnread ? 'text-white hover:bg-interactive-hover' : 'text-txt-tertiary hover:bg-interactive-hover hover:text-txt-secondary' }`; // Name: font-semibold for unread (deliberately NOT font-bold — design decision) const nameClass = `text-[15px] truncate leading-tight ${ isActive ? 'text-white font-medium' : isUnread ? 'text-white font-semibold' : 'text-txt-tertiary group-hover:text-txt-secondary font-medium' }`; // Timestamp: brightens on hover and lifts for unread/selected const timestampClass = `text-[11px] ml-auto flex-shrink-0 ${ isActive || isUnread ? 'text-txt-secondary' : 'text-txt-tertiary group-hover:text-txt-secondary' }`; // Preview: brightens on hover and lifts for unread/selected const previewClass = `text-[12px] truncate leading-tight mt-0.5 ${ isActive || isUnread ? 'text-txt-secondary' : 'text-txt-tertiary group-hover:text-txt-secondary' }`; // Federation badge: brightens with parent const fedBadgeClass = `flex-shrink-0 ${ isActive || isUnread ? 'text-txt-secondary/60' : 'text-txt-tertiary/60 group-hover:text-txt-secondary/60' }`; // Close button: always visible when selected, hover-reveal otherwise const closeClass = `${ isActive ? 'opacity-100' : 'opacity-0 group-hover:opacity-100' } text-txt-tertiary hover:text-txt-primary transition-opacity flex-shrink-0 ml-1`; // ── Preview text ────────────────────────────────────────────────────── const preview = formatDmPreview(dm.lastMessage ?? null); let previewText: string | null = null; if (isGroup) { const lastMsg = dm.lastMessage; const senderName = (lastMsg && 'user' in lastMsg ? lastMsg.user?.displayName : undefined) ?? dm.members.find(m => m.id === lastMsg?.userId)?.displayName ?? 'Unknown'; previewText = preview ? `${senderName}: ${preview}` : `${dm.members.length} Members`; } else { previewText = preview; } const itemJsx = (