File size: 3,806 Bytes
7e2f74d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | import React, { useState } from 'react';
import { Folder, FolderOpen, FileCode, ChevronRight, ChevronDown, GitBranch } from 'lucide-react';
function TreeNode({ name, node }) {
const isDir = node.type === 'directory';
const [open, setOpen] = useState(false);
const fmtSize = (b) => {
if (!b) return '';
if (b < 1024) return `${b}B`;
if (b < 1024 * 1024) return `${(b / 1024).toFixed(1)}KB`;
return `${(b / (1024 * 1024)).toFixed(1)}MB`;
};
return (
<div style={{ marginBottom: 1 }}>
<div
className="tree-item"
onClick={() => isDir && setOpen(!open)}
style={{ paddingLeft: 0 }}
>
{isDir ? (
<>
{open
? <ChevronDown size={13} style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
: <ChevronRight size={13} style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
}
{open
? <FolderOpen size={14} style={{ color: 'var(--accent-amber)', flexShrink: 0 }} />
: <Folder size={14} style={{ color: 'var(--accent-amber)', flexShrink: 0 }} />
}
<span style={{ fontWeight: 600, color: 'var(--text-primary)', fontSize: '0.8rem' }}>{name}</span>
</>
) : (
<>
<span style={{ width: 13, flexShrink: 0 }} />
<FileCode size={13} style={{ color: 'var(--accent-teal)', flexShrink: 0 }} />
<span className="file" style={{ fontSize: '0.78rem', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{name}
</span>
{node.size !== undefined && (
<span style={{ fontSize: '0.66rem', color: 'var(--text-muted)', marginLeft: 'auto', flexShrink: 0, paddingLeft: '0.25rem' }}>
{fmtSize(node.size)}
</span>
)}
</>
)}
</div>
{isDir && open && node.children && (
<div style={{ paddingLeft: '0.9rem', borderLeft: '1px solid var(--border-color)', marginLeft: '0.4rem' }}>
{Object.entries(node.children)
.sort(([, a], [, b]) => {
if (a.type === 'directory' && b.type !== 'directory') return -1;
if (a.type !== 'directory' && b.type === 'directory') return 1;
return 0;
})
.map(([n, c]) => <TreeNode key={c.path || n} name={n} node={c} />)
}
</div>
)}
</div>
);
}
export default function RepoTree({ tree, title }) {
if (!tree || Object.keys(tree).length === 0) {
return (
<div style={{ color: 'var(--text-muted)', fontSize: '0.85rem', textAlign: 'center', padding: '2rem 0' }}>
No file tree available.
</div>
);
}
return (
<div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.4rem',
fontFamily: 'var(--font-display)',
fontSize: '0.82rem',
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: '0.05em',
color: 'var(--text-muted)',
marginBottom: '0.75rem',
paddingBottom: '0.5rem',
borderBottom: '1px solid var(--border-color)',
}}>
<GitBranch size={13} style={{ color: 'var(--accent-teal)' }} />
{title || 'Repository'}
</div>
<div style={{ overflow: 'auto', maxHeight: 'calc(100vh - 220px)', paddingRight: '0.25rem' }}>
{Object.entries(tree)
.sort(([, a], [, b]) => {
if (a.type === 'directory' && b.type !== 'directory') return -1;
if (a.type !== 'directory' && b.type === 'directory') return 1;
return 0;
})
.map(([n, node]) => <TreeNode key={node.path || n} name={n} node={node} />)
}
</div>
</div>
);
}
|