import { Fragment, useEffect, useRef } from "react"; import { IconFolder, IconFile, IconPhoto } from "@tabler/icons-react"; import { cn } from "@/lib/utils"; import { mentionMatchSpans, type MentionMatchSpan } from "@/lib/mention-match"; export interface FileItem { name: string; path: string; isDirectory: boolean; matchPositions?: number[]; } interface FilePickerMenuProps { items: FileItem[]; selectedIndex: number; isLoading?: boolean; isStale?: boolean; showMediaOption?: boolean; onSelectMedia?: () => void; onSelectItem: (item: FileItem) => void; onHover: (index: number) => void; } function parentDir(path: string): string { const trimmed = path.endsWith("/") ? path.slice(0, -1) : path; const idx = trimmed.lastIndexOf("/"); return idx === -1 ? "" : trimmed.slice(0, idx); } function nameSpans(item: FileItem): MentionMatchSpan[] { const path = item.path.endsWith("/") ? item.path.slice(0, -1) : item.path; return mentionMatchSpans(item.name, item.matchPositions, Math.max(0, path.length - item.name.length)); } function dirSpans(item: FileItem): MentionMatchSpan[] { return mentionMatchSpans(parentDir(item.path), item.matchPositions, 0); } export function FilePickerMenu({ items, selectedIndex, isLoading, isStale = false, showMediaOption = true, onSelectMedia, onSelectItem, onHover, }: FilePickerMenuProps) { const selectedRef = useRef(null); const hoverSelectionRef = useRef(null); useEffect(() => { if (hoverSelectionRef.current === selectedIndex) { hoverSelectionRef.current = null; return; } hoverSelectionRef.current = null; selectedRef.current?.scrollIntoView({ block: "nearest" }); }, [selectedIndex]); const handleHover = (index: number) => { if (isStale) return; hoverSelectionRef.current = index; onHover(index); }; const headerCount = showMediaOption ? 1 : 0; return (
{showMediaOption && onSelectMedia && ( )}
{isLoading ? (
Loading…
) : items.length === 0 ? (
No files found
) : ( items.map((item, idx) => { const itemIndex = idx + headerCount; const dir = parentDir(item.path); return ( ); }) )}
); }