"use client"; import { useState } from "react"; import { ChevronRight, ChevronDown, FileText, File, Folder, FolderOpen, } from "lucide-react"; import { cn } from "@/lib/utils"; // --------------------------------------------------------------------------- // Tree data structures // --------------------------------------------------------------------------- interface FileTreeNode { name: string; path: string; isDirectory: boolean; children: FileTreeNode[]; } function buildTree(filePaths: string[]): FileTreeNode[] { const root: FileTreeNode[] = []; for (const filePath of filePaths) { const parts = filePath.split("/"); let current = root; for (let i = 0; i < parts.length; i++) { const name = parts[i]!; const isLast = i === parts.length - 1; const path = parts.slice(0, i + 1).join("/"); let existing = current.find((n) => n.name === name); if (!existing) { existing = { name, path, isDirectory: !isLast, children: [], }; current.push(existing); } if (!isLast) { current = existing.children; } } } function sortNodes(nodes: FileTreeNode[]): FileTreeNode[] { nodes.sort((a, b) => { if (a.path === "SKILL.md") return -1; if (b.path === "SKILL.md") return 1; if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1; return a.name.localeCompare(b.name); }); for (const node of nodes) { if (node.isDirectory) sortNodes(node.children); } return nodes; } return sortNodes(root); } function getFileIcon(name: string) { if (name.endsWith(".md") || name.endsWith(".mdx")) return FileText; return File; } // --------------------------------------------------------------------------- // Tree node renderer // --------------------------------------------------------------------------- function TreeNodeItem({ node, selectedPath, onSelect, depth = 0, }: { node: FileTreeNode; selectedPath: string; onSelect: (path: string) => void; depth?: number; }) { const [expanded, setExpanded] = useState(true); const isSelected = node.path === selectedPath; if (node.isDirectory) { const FolderIcon = expanded ? FolderOpen : Folder; const ChevronIcon = expanded ? ChevronDown : ChevronRight; return (
{expanded && (
{node.children.map((child) => ( ))}
)}
); } const Icon = getFileIcon(node.name); return ( ); } // --------------------------------------------------------------------------- // Public component // --------------------------------------------------------------------------- export function FileTree({ filePaths, selectedPath, onSelect, }: { filePaths: string[]; selectedPath: string; onSelect: (path: string) => void; }) { const tree = buildTree(filePaths); if (tree.length === 0) { return (

No files

); } return (
{tree.map((node) => ( ))}
); }