back to lab
File Tree
A recursive file tree component with collapsible folders, file type icons, and interactive schema input. Inspired by interview qn
Use 2 spaces or tabs for indentation to create nested folders.
Preview
Source Code
import { useState } from "react";
// Types
interface FileTreeNode {
name: string;
type: "file" | "folder";
children?: FileTreeNode[];
}
// Returns emoji icon based on file extension
function getFileIcon(filename: string): string {
const ext = filename.split(".").pop()?.toLowerCase();
const iconMap: Record<string, string> = {
json: "📋", ts: "🔷", tsx: "🔷", js: "🟨", jsx: "🟨",
css: "🎨", scss: "🎨", md: "📝", mdx: "📝",
png: "🖼️", jpg: "🖼️", svg: "🖼️", html: "🌐",
env: "⚙️", config: "⚙️",
};
return iconMap[ext || ""] || "📄";
}
// Parses indentation-based text to tree structure
function parseSchemaToTree(schema: string): FileTreeNode[] {
const lines = schema.split("\n").filter((line) => line.trim());
const root: FileTreeNode[] = [];
const stack: { node: FileTreeNode; indent: number }[] = [];
for (const line of lines) {
const match = line.match(/^(\s*)/);
const indent = match ? match[1].replace(/\t/g, " ").length : 0;
const name = line.trim();
if (!name) continue;
const newNode: FileTreeNode = { name, type: "file" };
while (stack.length > 0 && stack[stack.length - 1].indent >= indent) {
stack.pop();
}
if (stack.length === 0) {
root.push(newNode);
} else {
const parent = stack[stack.length - 1].node;
parent.type = "folder";
if (!parent.children) parent.children = [];
parent.children.push(newNode);
}
stack.push({ node: newNode, indent });
}
return root;
}
// Recursive component - renders itself for children
function FileTreeNode({ node }: { node: FileTreeNode }) {
const [isOpen, setIsOpen] = useState(false);
const isFolder = node.type === "folder";
return (
<div style={{ marginLeft: 20 }}>
<div
onClick={() => isFolder && setIsOpen(!isOpen)}
style={{
cursor: isFolder ? "pointer" : "default",
padding: "4px 8px",
display: "flex",
alignItems: "center",
gap: "6px",
}}
>
{isFolder ? (isOpen ? "📂" : "📁") : getFileIcon(node.name)}
<span>{node.name}</span>
</div>
{isFolder && isOpen && node.children?.map((child, i) => (
<FileTreeNode key={`${child.name}-${i}`} node={child} />
))}
</div>
);
}
// Main component
export default function FileTree({ data }: { data: FileTreeNode[] }) {
return (
<div>
{data.map((node, i) => (
<FileTreeNode key={`${node.name}-${i}`} node={node} />
))}
</div>
);
}
export { parseSchemaToTree, type FileTreeNode };