29 / 122 · Tool use
File tree
Everything a run touched, as a tree, with the churn spelled out per file.
4 files changed+78 −9
Installation
npx shadcn@latest add "@assistant-ui/elements-file-tree"
Usage
import { FileTree } from "@/components/elements/file-tree";
<FileTree
nodes={nodes}
visibleCount={nodes.length}
totalAdditions={78}
totalDeletions={9}
/>Props
nodes*FileTreeNode[]Flat list in display order. Nesting comes from each node's depth, not from children arrays.
visibleCount*numberHow many nodes have landed. Raise it as the run touches more files.
totalAdditions*numberAdded lines across the whole change.
totalDeletions*numberRemoved lines across the whole change.
classNamestringExtra classes merged onto the root.
Source
file-tree.tsx"use client";
import { ChevronDownIcon, FileIcon, FolderIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { mono, paper } from "./surfaces";
export interface FileTreeNode {
path: string;
name: string;
depth: number;
kind: "folder" | "file";
additions?: number;
deletions?: number;
}
export function FileTree({
nodes,
visibleCount,
totalAdditions,
totalDeletions,
className,
}: {
nodes: readonly FileTreeNode[];
visibleCount: number;
totalAdditions: number;
totalDeletions: number;
className?: string;
}) {
const files = nodes.filter((node) => node.kind === "file").length;
return (
<div
className={cn(
paper,
"flex w-full max-w-sm flex-col gap-2 rounded-2xl p-3.5",
className,
)}
>
<div className="flex items-baseline justify-between px-1">
<span className="text-[13.5px] font-medium">{files} files changed</span>
<span className={cn(mono, "tabular-nums")}>
<span className="text-emerald-600 dark:text-emerald-400">
+{totalAdditions}
</span>{" "}
<span className="text-red-600 dark:text-red-400">
−{totalDeletions}
</span>
</span>
</div>
<div className="flex flex-col">
{nodes.slice(0, visibleCount).map((node) => (
<div
key={node.path}
className="fade-in slide-in-from-left-1 animate-in fill-mode-both hover:bg-foreground/[0.03] flex items-center gap-2 rounded-lg px-1 py-1 text-[13px] transition-colors duration-300"
style={{ paddingInlineStart: `${0.25 + node.depth * 0.85}rem` }}
>
{node.kind === "folder" ? (
<>
<ChevronDownIcon className="text-foreground/25 size-3 shrink-0" />
<FolderIcon className="text-foreground/35 size-3.5 shrink-0" />
<span className="text-foreground/60 min-w-0 flex-1 truncate">
{node.name}
</span>
</>
) : (
<>
<FileIcon className="text-foreground/30 ms-3 size-3.5 shrink-0" />
<span className="text-foreground/85 min-w-0 flex-1 truncate">
{node.name}
</span>
<span className={cn(mono, "shrink-0 tabular-nums")}>
{node.additions ? (
<span className="text-emerald-600 dark:text-emerald-400">
+{node.additions}
</span>
) : null}{" "}
{node.deletions ? (
<span className="text-red-600 dark:text-red-400">
−{node.deletions}
</span>
) : null}
</span>
</>
)}
</div>
))}
</div>
</div>
);
}