import { useEffect, useRef } from 'react'; import { FileIcon } from '@renderer/components/team/editor/FileIcon'; import { MemberBadge } from '@renderer/components/team/MemberBadge'; import { getTeamColorSet, getThemedText } from '@renderer/constants/teamColors'; import { useTheme } from '@renderer/hooks/useTheme'; import { nameColorSet } from '@renderer/utils/projectColor'; import { Command, Folder, Hash, Loader2, UsersRound } from 'lucide-react'; import type { MentionSuggestion } from '@renderer/types/mention'; interface MentionSuggestionListProps { suggestions: MentionSuggestion[]; selectedIndex: number; onSelect: (s: MentionSuggestion) => void; query: string; /** When true, adjusts empty state text to mention files */ hasFileSearch?: boolean; /** When true, shows a loading spinner for file search */ filesLoading?: boolean; } const HighlightedName = ({ name, query }: { name: string; query: string }): React.JSX.Element => { if (!query) return {name}; const lower = name.toLowerCase(); const qLower = query.toLowerCase(); const idx = lower.indexOf(qLower); if (idx < 0) return {name}; const before = name.slice(0, idx); const match = name.slice(idx, idx + query.length); const after = name.slice(idx + query.length); return ( {before} {match} {after} ); }; /** Section header for grouped suggestion lists */ const SectionHeader = ({ label }: { label: string }): React.JSX.Element => (
  • {label}
  • ); export const MentionSuggestionList = ({ suggestions, selectedIndex, onSelect, query, hasFileSearch, filesLoading, }: MentionSuggestionListProps): React.JSX.Element => { const listRef = useRef(null); const { isLight } = useTheme(); useEffect(() => { const list = listRef.current; if (!list) return; // Query by role=option to skip section headers const options = list.querySelectorAll('[role="option"]'); const selected = options[selectedIndex] as HTMLElement | undefined; selected?.scrollIntoView({ block: 'nearest' }); }, [selectedIndex]); if (suggestions.length === 0) { const emptyStateText = filesLoading ? 'Searching...' : hasFileSearch ? 'No matching suggestions' : 'No matching suggestions'; return (
    {emptyStateText}
    ); } // Categorize suggestions (folders are grouped with files) type Section = 'member' | 'team' | 'task' | 'file' | 'command'; const getSuggestionSection = (s: MentionSuggestion): Section => { if (s.type === 'file' || s.type === 'folder') return 'file'; if (s.type === 'task') return 'task'; if (s.type === 'command') return 'command'; if (s.type === 'team') return 'team'; return 'member'; }; const sectionLabel: Record = { member: 'Members', team: 'Teams', task: 'Tasks', file: 'Files', command: 'Commands', }; // Determine which sections are present const presentSections = new Set(suggestions.map(getSuggestionSection)); const showSections = presentSections.size > 1; // Build items with section headers inserted const items: React.JSX.Element[] = []; let currentSection: Section | null = null; let optionIndex = 0; for (const s of suggestions) { const section = getSuggestionSection(s); const isFile = s.type === 'file'; const isFolder = s.type === 'folder'; const isFileOrFolder = isFile || isFolder; const isTeam = section === 'team'; const isTask = section === 'task'; const isCommand = section === 'command'; const taskTeamColorSet = isTask && s.color ? getTeamColorSet(s.color) : isTask && s.teamDisplayName ? nameColorSet(s.teamDisplayName, isLight) : null; // Insert section header on transition if (showSections && section !== currentSection) { items.push(); currentSection = section; } const isSelected = optionIndex === selectedIndex; const colorSet = isFileOrFolder ? null : s.color ? getTeamColorSet(s.color) : isTeam ? nameColorSet(s.name) : null; const idx = optionIndex; optionIndex++; items.push(
  • { e.preventDefault(); onSelect(s); }} > {isFolder ? ( ) : isFile ? ( ) : isTask ? ( ) : isCommand ? ( ) : isTeam ? ( ) : ( )}
    {!isTask && !isFileOrFolder && s.subtitle ? ( {s.subtitle} ) : null} {isTask && s.ownerName ? ( ) : null} {isTask && s.teamDisplayName ? ( {s.teamDisplayName} ) : null}
    {isTask && s.subtitle ? (
    {s.subtitle}
    ) : null} {isCommand && s.description ? (
    {s.description}
    ) : null}
    {isTeam && s.isOnline !== undefined ? ( ) : null} {s.subtitle && isFileOrFolder ? ( {'\u200E' + s.subtitle} ) : null}
  • ); } return (
      {items} {filesLoading ? (
    • Searching files...
    • ) : null}
    ); };