import React from 'react'; import { ArrowUp } from 'lucide-react'; import type { INode } from './types'; import { FOCUS_BG, FOCUS_BORDER, SELECTED_BG, SELECTED_BORDER } from './types'; import { getMimeCategory, CATEGORY_STYLE, formatSize, formatDate } from './helpers'; import { NodeIcon } from './ThumbPreview'; // ── Props ──────────────────────────────────────────────────────── interface FileListViewProps { listRef: React.RefObject; sorted: INode[]; canGoUp: boolean; goUp: () => void; focusIdx: number; setFocusIdx: (idx: number) => void; selected: INode[]; onItemClick: (idx: number, e?: React.MouseEvent) => void; onItemDoubleClick: (idx: number) => void; fontSize: number; mode: string; /** Current type-ahead search buffer for visual highlight */ searchBuffer?: string; isSearchMode?: boolean; } // ── Component ──────────────────────────────────────────────────── const FileListView: React.FC = ({ listRef, sorted, canGoUp, goUp, focusIdx, setFocusIdx, selected, onItemClick, onItemDoubleClick, fontSize, mode, searchBuffer, isSearchMode }) => (
{canGoUp && !isSearchMode && (
setFocusIdx(0)} onDoubleClick={goUp} data-testid="file-list-node-up" className="fb-row" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 10px', cursor: 'pointer', fontSize, borderBottom: '1px solid rgba(255,255,255,0.06)', background: focusIdx === 0 ? FOCUS_BG : 'transparent', borderLeftWidth: 2, borderLeftColor: 'transparent', borderLeftStyle: 'solid', outline: focusIdx === 0 ? `2px solid ${FOCUS_BORDER}` : 'none', outlineOffset: '-2px', }}> ..
)} {sorted.map((node, i) => { // Note: in search mode, canGoUp is usually false, or we explicitly don't shift index by 1 since there is no ".." node rendered const idx = ((canGoUp && !isSearchMode) ? i + 1 : i); const isDir = getMimeCategory(node) === 'dir'; const isFocused = focusIdx === idx; const isSelected = selected.some(sel => sel.path === node.path); return (
onItemClick(idx, e)} onDoubleClick={() => onItemDoubleClick(idx)} className="fb-row" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: isSearchMode ? '8px 10px' : '5px 10px', cursor: 'pointer', fontSize, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.06)', borderBottomStyle: 'solid', background: isSelected ? SELECTED_BG : isFocused ? FOCUS_BG : 'transparent', borderLeftWidth: 2, borderLeftColor: isSelected ? SELECTED_BORDER : 'transparent', borderLeftStyle: isSelected ? 'outset' : 'solid', outline: isFocused ? `2px solid ${FOCUS_BORDER}` : 'none', outlineOffset: '-2px', }}>
{(() => { if (!isFocused || !searchBuffer) return node.name; const lower = node.name.toLowerCase(); const pos = lower.startsWith(searchBuffer) ? 0 : lower.indexOf(searchBuffer); if (pos < 0) return node.name; const isDark = document.documentElement.classList.contains('dark'); const hlStyle = { background: isDark ? 'rgba(14,165,233,0.3)' : 'rgba(245,158,11,0.25)', color: isDark ? '#bae6fd' : '#92400e', borderBottom: isDark ? '1px solid #38bdf8' : '1px solid #f59e0b', }; return ( <> {pos > 0 && node.name.slice(0, pos)} {node.name.slice(pos, pos + searchBuffer.length)} {node.name.slice(pos + searchBuffer.length)} ); })()} {isSearchMode && ( {node.parent || '/'} )}
{node.size !== undefined && ( {formatSize(node.size)} )} {mode === 'advanced' && node.mtime && ( {formatDate(node.mtime)} )}
); })}
); export default FileListView;