PD02▸📊 Data Display · 資料展示▸樹狀
Tree View
樹狀檢視
難度 ★★★★★標籤 tree · 巢狀 · expand4 min read
模式簡述
Tree View:階層資料的展開/收合。檔案管理、組織結構、技術分類常用。
Claude Code Prompt
參考:PD02 tree 組件:TreeView.tsx Tree View:階層資料的展開/收合。檔案管理、組織結構、技術分類常用。 限制: - 展開全部按鈕沒做 - 沒記住展開狀態 - 鍵盤導航沒做
完整程式碼
"use client";
import { useEffect, useState } from "react";
import { ChevronRight, ChevronDown, Folder, FileText } from "lucide-react";
type State = "collapsed" | "partial" | "all-open";
type Node = { name: string; children?: Node[] };
const TREE: Node[] = [
{
name: "src",
children: [
{
name: "components",
children: [
{ name: "Modal.tsx" },
{ name: "Tabs.tsx" },
],
},
{ name: "app", children: [{ name: "page.tsx" }, { name: "layout.tsx" }] },
],
},
{ name: "content", children: [{ name: "patterns" }, { name: "effects" }] },
{ name: "README.md" },
];
function TreeNode({
node,
level,
expanded,
toggle,
path,
}: {
node: Node;
level: number;
expanded: Set<string>;
toggle: (p: string) => void;
path: string;
}) {
const hasChildren = node.children && node.children.length > 0;
const isOpen = expanded.has(path);
return (
<div>
<button
onClick={() => hasChildren && toggle(path)}
className="flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-xs hover:bg-background/60"
style={{ paddingLeft: 4 + level * 14 }}
>
{hasChildren ? (
isOpen ? (
<ChevronDown className="h-3 w-3 shrink-0 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground" />
)
) : (
<span className="w-3" />
)}
{hasChildren ? (
<Folder className="h-3.5 w-3.5 shrink-0 text-lab-accent" />
) : (
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
)}
<span>{node.name}</span>
</button>
{hasChildren && isOpen && (
<div>
{node.children!.map((c, i) => (
<TreeNode
key={i}
node={c}
level={level + 1}
expanded={expanded}
toggle={toggle}
path={`${path}/${c.name}`}
/>
))}
</div>
)}
</div>
);
}
function collectAllPaths(nodes: Node[], parent = ""): string[] {
return nodes.flatMap((n) => {
const p = `${parent}/${n.name}`;
return n.children ? [p, ...collectAllPaths(n.children, p)] : [];
});
}
export function TreeView({ state = "partial" }: { state?: State }) {
const [expanded, setExpanded] = useState<Set<string>>(new Set());
useEffect(() => {
if (state === "collapsed") setExpanded(new Set());
else if (state === "partial") setExpanded(new Set(["/src", "/content"]));
else setExpanded(new Set(collectAllPaths(TREE)));
}, [state]);
return (
<div className="w-full max-w-sm rounded-lg border border-border bg-background/30 p-2">
{TREE.map((n, i) => (
<TreeNode
key={i}
node={n}
level={0}
expanded={expanded}
toggle={(p) =>
setExpanded((prev) => {
const next = new Set(prev);
if (next.has(p)) next.delete(p);
else next.add(p);
return next;
})
}
path={`/${n.name}`}
/>
))}
</div>
);
}何時用
- ✅ 檔案系統、資料夾結構
- ✅ 組織架構、權限階層
- ✅ 巢狀分類(書籍章節、技術領域)
何時別用
- ❌ 扁平資料(用 list)
- ❌ 層級超過 5 層(使用者迷路,改用麵包屑或搜尋)
- ❌ 手機優先(樹太擠,改用 drill-down nav)
我踩過的坑
-
展開全部按鈕沒做:節點多時手動點到底很累。應提供「全展開/全收合」。
-
沒記住展開狀態:使用者切頁回來全部重置。存到 localStorage 或 URL。
-
鍵盤導航沒做:ArrowRight 展開、Left 收合、Up/Down 切換 focus。
相關模式
- PC03
- PD08