mono/packages/ui/src/modules/storage/FileGridView.tsx
2026-03-21 20:18:25 +01:00

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;