109 lines
5.7 KiB
TypeScript
109 lines
5.7 KiB
TypeScript
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 } from './helpers';
|
|
import { NodeIcon, ThumbPreview } from './ThumbPreview';
|
|
|
|
// ── Props ────────────────────────────────────────────────────────
|
|
|
|
interface FileGridViewProps {
|
|
listRef: React.RefObject<HTMLDivElement>;
|
|
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;
|
|
thumbSize: number;
|
|
mount: string;
|
|
tokenParam: string;
|
|
fontSize: number;
|
|
isSearchMode?: boolean;
|
|
}
|
|
|
|
// ── Component ────────────────────────────────────────────────────
|
|
|
|
const FileGridView: React.FC<FileGridViewProps> = ({
|
|
listRef, sorted, canGoUp, goUp, focusIdx, setFocusIdx,
|
|
selected, onItemClick, onItemDoubleClick, thumbSize, mount, tokenParam, fontSize, isSearchMode
|
|
}) => (
|
|
<div ref={listRef as any} data-testid="file-grid-view" style={{
|
|
overflowY: 'auto', flex: 1, minHeight: 0, display: 'grid',
|
|
gridTemplateColumns: `repeat(auto-fill, minmax(${thumbSize}px, 1fr))`,
|
|
gridAutoRows: 'max-content',
|
|
gap: 12, padding: 16, alignContent: 'start',
|
|
}}>
|
|
{canGoUp && !isSearchMode && (
|
|
<div data-fb-idx={0} onClick={() => setFocusIdx(0)} onDoubleClick={goUp} className="fb-thumb"
|
|
data-testid="file-grid-node-up"
|
|
style={{
|
|
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
|
padding: 2, borderRadius: 2, cursor: 'pointer', gap: 2, aspectRatio: '1',
|
|
borderWidth: 1, borderColor: 'transparent',
|
|
borderStyle: 'solid',
|
|
background: focusIdx === 0 ? FOCUS_BG : 'transparent',
|
|
outline: focusIdx === 0 ? `2px solid ${FOCUS_BORDER}` : 'none',
|
|
outlineOffset: '2px',
|
|
}}>
|
|
<ArrowUp size={24} style={{ color: CATEGORY_STYLE.dir.color }} />
|
|
<span style={{ fontSize: fontSize }}>..</span>
|
|
</div>
|
|
)}
|
|
{sorted.map((node, i) => {
|
|
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 (
|
|
<div key={node.path || node.name} data-fb-idx={idx}
|
|
data-testid="file-grid-node"
|
|
data-node-id={node.path || node.name}
|
|
onClick={(e) => onItemClick(idx, e)}
|
|
onDoubleClick={() => onItemDoubleClick(idx)}
|
|
className="fb-thumb" style={{
|
|
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
|
padding: 6, borderRadius: 6, cursor: 'pointer', gap: 6, overflow: 'hidden',
|
|
background: isSelected ? 'rgba(59, 130, 246, 0.05)' : 'transparent',
|
|
}}>
|
|
<div style={{
|
|
width: '100%', aspectRatio: '1/1',
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
borderRadius: 6, overflow: 'hidden',
|
|
borderWidth: isSelected ? 0 : 1,
|
|
borderColor: isSelected ? SELECTED_BORDER : 'transparent',
|
|
borderStyle: isSelected ? 'solid' : 'solid',
|
|
outline: isFocused ? `2px solid ${FOCUS_BORDER}` : 'none',
|
|
outlineOffset: '2px',
|
|
}}>
|
|
{isDir ? <NodeIcon node={node} size={Math.max(24, Math.floor(thumbSize * 0.5))} /> : <ThumbPreview node={node} mount={mount} tokenParam={tokenParam} thumbSize={thumbSize} />}
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', alignItems: 'center', gap: 2 }}>
|
|
<span style={{
|
|
fontSize: fontSize, textAlign: 'center', width: '100%',
|
|
overflow: 'hidden', textOverflow: 'ellipsis',
|
|
display: '-webkit-box', WebkitLineClamp: isSearchMode ? 1 : 2, WebkitBoxOrient: 'vertical',
|
|
wordBreak: 'break-word', lineHeight: 1.3,
|
|
minHeight: isSearchMode ? fontSize * 1.3 : fontSize * 1.3 * 2,
|
|
}}>
|
|
{node.name}
|
|
</span>
|
|
{isSearchMode && (
|
|
<span style={{
|
|
fontSize: Math.max(10, fontSize * 0.8), color: 'var(--muted-foreground)', textAlign: 'center', width: '100%',
|
|
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'
|
|
}}>
|
|
{node.parent || '/'}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
|
|
export default FileGridView;
|