nodes + renderItem
트리 구조는 nodes로 전달하고, 각 항목의 생김새는 renderItem으로 직접 그립니다. 펼침·선택·키보드 같은 트리 동작은 컴포넌트가 알아서 처리합니다.
단일 선택
selectedId로 지정한 노드 하나를 현재 위치로 강조합니다. 강조는 왼쪽 보더 대신 은은한 배경 채움으로 표시해 다른 컴포넌트와 결을 맞춥니다.
펼침 제어
처음 펼쳐 둘 노드는 defaultExpandedIds로 지정합니다. 펼침 상태를 직접 다뤄야 하면 expandedIds와 onExpandedChange로 제어할 수 있습니다.
키보드 탐색
방향키로 항목을 옮기고 펼치거나 접으며, Home·End로 처음과 끝으로 이동합니다. Enter로 선택하고, 하위가 있는 노드는 Space로 펼치거나 접습니다. Tab 한 번으로 트리에 들어오는 roving tabindex 등 WAI-ARIA tree 패턴을 따릅니다.

폴더 트리

defaultExpandedIds, selectedId, 펼침/접힘
  • 해부학
    • 두경부
  • 생리학
코드 보기tsx
import { Folder, FolderOpen } from "lucide-react";
import { TreeView, type TreeNode } from "@olundot/ui";

interface FolderNode extends TreeNode {
  name: string;
  children?: FolderNode[];
}

const folders: FolderNode[] = [
  {
    id: "anatomy",
    name: "해부학",
    children: [
      { id: "anatomy-head", name: "두경부" },
      { id: "anatomy-thorax", name: "흉부", children: [{ id: "anatomy-heart", name: "심장" }] },
    ],
  },
  { id: "physiology", name: "생리학" },
];

export function FolderTree() {
  return (
    <TreeView
      aria-label="콘텐츠 폴더"
      nodes={folders}
      selectedId="anatomy-head"
      defaultExpandedIds={["anatomy"]}
      renderItem={(node, state) => (
        <>
          {state.hasChildren ? (state.expanded ? <FolderOpen size={16} strokeWidth={1.75} /> : <Folder size={16} strokeWidth={1.75} />) : <Folder size={16} strokeWidth={1.75} />}
          <span className="truncate">{node.name}</span>
        </>
      )}
    />
  );
}

제어형 선택

selectedId·expandedIds·onSelect·onExpandedChange
  • 해부학
    • 두경부
  • 생리학
코드 보기tsx
import { useState } from "react";
import { Folder, FolderOpen } from "lucide-react";
import { TreeView, type TreeNode } from "@olundot/ui";

interface FolderNode extends TreeNode {
  name: string;
  children?: FolderNode[];
}

export function ControlledTree({ folders }: { folders: FolderNode[] }) {
  const [selectedId, setSelectedId] = useState("anatomy-head");
  const [expandedIds, setExpandedIds] = useState<string[]>(["anatomy"]);

  return (
    <TreeView
      aria-label="제어형 폴더 트리"
      nodes={folders}
      selectedId={selectedId}
      expandedIds={expandedIds}
      onSelect={(id) => setSelectedId(id)}
      onExpandedChange={setExpandedIds}
      renderItem={(node, state) => (
        <>
          {state.expanded ? <FolderOpen size={16} strokeWidth={1.75} /> : <Folder size={16} strokeWidth={1.75} />}
          <span className="truncate">{node.name}</span>
        </>
      )}
    />
  );
}
renderItem에 링크(현재 폴더 강조)
코드 보기tsx
import Link from "next/link";
import { Folder } from "lucide-react";
import { TreeView, type TreeNode } from "@olundot/ui";

interface FolderNode extends TreeNode {
  name: string;
  href: string;
  children?: FolderNode[];
}

// 항목 라벨이 링크일 때: treeitem wrapper가 포커스를 소유하므로 자식 링크는
// tabIndex={-1}로 두어 단일 Tab stop을 유지한다. 포인터 클릭 시 이동은 그대로 동작하고,
// 키보드 사용자는 Enter(onSelect)에서 라우팅한다.
export function LinkedTree({ folders, currentId }: { folders: FolderNode[]; currentId: string }) {
  return (
    <TreeView
      aria-label="폴더 내비게이션"
      nodes={folders}
      selectedId={currentId}
      selectionAriaCurrent="page"
      defaultExpandedIds={["anatomy"]}
      renderItem={(node) => (
        <>
          <Folder size={16} strokeWidth={1.75} />
          <Link href={node.href} tabIndex={-1} className="truncate">
            {node.name}
          </Link>
        </>
      )}
    />
  );
}