insignia
back to logs

FILE TREE

a recursive file tree component built from an interview question. paste an indentation-based schema on the left and see the collapsible folder structure render live on the right.

use 2 spaces or tabs for nesting.

Preview
src
app
layout.tsx
page.tsx
globals.css
components
package.json
tsconfig.json

Source

"use client";
import { useState, useCallback } from "react";
interface FileTreeNode {
name: string;
children?: FileTreeNode[];
}
function getFileIcon(filename: string) {
const ext = filename.split(".").pop()?.toLowerCase();
const map: Record<string, string> = {
json: "📋", ts: "🔷", tsx: "🔷", js: "🟨", jsx: "🟨",
css: "🎨", md: "📝", mdx: "📝", png: "🖼️", svg: "🖼️",
};
return map[ext || ""] || "📄";
}
function TreeNode({ node, depth, path, expanded, onToggle }) {
const isFolder = Boolean(node.children?.length);
const isOpen = expanded.has(path);
return (
<div>
<div
onClick={() => isFolder && onToggle(path)}
style={{ paddingLeft: depth * 12 + 8, cursor: isFolder ? "pointer" : "default" }}
className="flex items-center gap-1.5 py-1 text-sm font-mono hover:bg-accent/50 rounded"
>
{isFolder ? (isOpen ? "📂" : "📁") : getFileIcon(node.name)}
<span>{node.name}</span>
</div>
{isFolder && isOpen && node.children.map((child, i) => (
<TreeNode key={i} node={child} depth={depth + 1}
path={`${path}/${child.name}`} expanded={expanded} onToggle={onToggle} />
))}
</div>
);
}
export function FileTree({ data }) {
const [expanded, setExpanded] = useState(new Set(["src"]));
const toggle = useCallback((p) =>
setExpanded(prev => { const n = new Set(prev); n.has(p) ? n.delete(p) : n.add(p); return n; }), []);
return (
<div className="rounded-lg border p-2 font-mono text-sm">
{data.map((node, i) => (
<TreeNode key={i} node={node} depth={0} path={node.name} expanded={expanded} onToggle={toggle} />
))}
</div>
);
}