Codeki / frontend /src /components /FileTree.tsx
Mayank2027's picture
Create frontend/src/components/FileTree.tsx
b094925 verified
Raw
History Blame Contribute Delete
2.78 kB
import React, { useState, useEffect } from 'react';
import { FaFolder, FaFolderOpen, FaFile, FaChevronRight, FaChevronDown, FaSpinner } from 'react-icons/fa';
import axios from 'axios';
interface TreeNode {
name: string;
path: string;
type: 'file' | 'folder';
children?: TreeNode[];
}
const FileTree: React.FC<{ projectPath: string; onFileSelect: (path: string) => void }> = ({ projectPath, onFileSelect }) => {
const [tree, setTree] = useState<TreeNode[]>([]);
const [expanded, setExpanded] = useState<Set<string>>(new Set());
const [selected, setSelected] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
axios.get('/api/files', { params: { project_path: projectPath } })
.then(res => setTree(res.data))
.catch(() => setTree([]))
.finally(() => setLoading(false));
}, [projectPath]);
const toggle = (path: string) => {
setExpanded(prev => {
const next = new Set(prev);
if (next.has(path)) next.delete(path); else next.add(path);
return next;
});
};
const handleClick = (node: TreeNode) => {
if (node.type === 'folder') {
toggle(node.path);
} else {
setSelected(node.path);
onFileSelect(node.path);
}
};
const renderNode = (node: TreeNode, depth: number = 0) => {
const isOpen = expanded.has(node.path);
return (
<div key={node.path}>
<div
onClick={() => handleClick(node)}
style={{
paddingLeft: depth * 16 + 8,
padding: '4px 8px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: 6,
background: selected === node.path ? 'var(--accent)' : 'transparent',
color: selected === node.path ? 'white' : 'var(--text-primary)',
fontSize: 13,
}}
>
{node.type === 'folder' ? (
<>
{isOpen ? <FaChevronDown size={10} /> : <FaChevronRight size={10} />}
{isOpen ? <FaFolderOpen size={14} /> : <FaFolder size={14} />}
</>
) : (
<>
<span style={{ width: 10 }} />
<FaFile size={14} />
</>
)}
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{node.name}</span>
</div>
{node.type === 'folder' && isOpen && node.children?.map(child => renderNode(child, depth + 1))}
</div>
);
};
if (loading) return <div style={{ padding: 12, color: 'var(--text-secondary)' }}><FaSpinner className="spin" /> Loading files...</div>;
return <div style={{ overflowY: 'auto', flex: 1 }}>{tree.map(node => renderNode(node))}</div>;
};
export default FileTree;