IDE@paxeer/ide/file-treeimplemented
File tree
Nested workspace tree with expand, selection, and git status chips.
Install
$
pnpm exec designx add @paxeer/ide/file-treePreview
Loading preview...
Source code
components/designx/ide/file-tree.tsxtsx
"use client";
import { useState } from "react";
import { Button, StatusChip, cn } from "@paxeer/core";
export interface FileTreeNode {
id: string;
name: string;
kind: "file" | "folder";
children?: FileTreeNode[];
git?: "modified" | "added" | "untracked";
}
export interface FileTreeProps {
nodes: FileTreeNode[];
selectedId?: string;
onSelect?: (node: FileTreeNode) => void;
className?: string;
}
function gitTone(git?: FileTreeNode["git"]) {
if (git === "modified") return "tertiary" as const;
if (git === "added") return "primary" as const;
if (git === "untracked") return "secondary" as const;
return undefined;
}
function TreeNode({
node,
depth,
selectedId,
onSelect,
}: {
node: FileTreeNode;
depth: number;
selectedId?: string;
onSelect?: (node: FileTreeNode) => void;
}) {
const [open, setOpen] = useState(true);
const selected = selectedId === node.id;
const isFolder = node.kind === "folder";
return (
<li role="treeitem" aria-expanded={isFolder ? open : undefined} aria-selected={selected}>
<Button
variant="ghost"
onClick={() => {
if (isFolder) setOpen((value) => !value);
onSelect?.(node);
}}
style={{ paddingLeft: 8 + depth * 14 }}
className={cn(
"h-7 w-full justify-start rounded-none text-on-surface",
selected && "bg-secondary-container text-on-secondary-container",
)}
>
<span aria-hidden className="w-3 text-on-surface-variant">
{isFolder ? (open ? "▾" : "▸") : "·"}
</span>
<span className="truncate">{node.name}</span>
{node.git ? (
<StatusChip tone={gitTone(node.git)} className="ml-auto">
{node.git === "modified" ? "M" : node.git === "added" ? "A" : "U"}
</StatusChip>
) : null}
</Button>
{isFolder && open && node.children ? (
<ul role="group">
{node.children.map((child) => (
<TreeNode
key={child.id}
node={child}
depth={depth + 1}
selectedId={selectedId}
onSelect={onSelect}
/>
))}
</ul>
) : null}
</li>
);
}
export function FileTree({ nodes, selectedId, onSelect, className }: FileTreeProps) {
return (
<nav aria-label="Workspace files" className={className}>
<ul role="tree" className="py-1">
{nodes.map((node) => (
<TreeNode
key={node.id}
node={node}
depth={0}
selectedId={selectedId}
onSelect={onSelect}
/>
))}
</ul>
</nav>
);
}
Dependencies
Registry dependencies