Skip Navigation
docs
next page

Tree View

A tree view that assembles all the functionalities of the Accordion component to create a tree view.

Installation

Run the following command

pnpm dlx @shadext/cli add tree-view

Why there is two components?

The TreeView component has two different ways to be used, that you can choose , based on your choice , that you have picked in the installation section.

  • The first approach is the tree-view component that is based on the tree-view-api components, in other words it is a wrapper for devs that don't want to have an custom functionality, but you still have the access to customise the ui , some of the benifits are :

    • Full control over the UI.
    • All functionality is provided out of the box , including the (Virtualization , Selection , Expend/Collapse , Keyboard Navigation).
    • You can customise the functionality if you want.
  • The second approach is the tree-view-api component that is based on the radix-ui-accordion components, and it is the primitive api that you can use to build your own custom tree view, some of the benifits are:

    • Full control over the UI.
    • You can customise the functionality as you want.

API Reference

Props

The TreeView component accepts the following props:

proptypedefault value
initialSelectedId
string
--
expendAll
boolean
false
elements*
TreeViewElement[]
--
initialExpendedItems
string[]
--
openIcon
ReactNode
<FolderOpenIcon />
closeIcon
ReactNode
<FolderIcon />
fileIcon
ReactNode
<FileIcon />

TreeViewElement

The TreeViewElement object accepts the following props:

fieldtypedefault Value
id*
string
--
name*
string
--
children
TreeViewElement[]
--
isSelectable
boolean
--

Accessibility

Currently, the TreeView component is fully accessible and supports keyboard navigation.

Keyboard Navigation

keyDescription
Tab
enter a nested folder
Tab + Shift
exit the current selected folder
ArrowDown
Increments by one step horzintanly
ArrowUp
Decrements by one step horzintanly
Enter
Opens/Close a folder , select a file

Usage

The TreeView component has two different ways to be used, that you can choose , based on your choice , that you have picked in the installation section.

import { TreeView } from "@/components/extension/tree-view";
const elements = [
  {
    id: "1",
    name: "components",
    children: [
      {
        id: "2",
        name: "extension",
        children: [
          {
            id: "3",
            name: "tree-view.tsx",
          },
          {
            id: "4",
            name: "tree-view-api.tsx",
          },
        ],
      },
      {
        id: "5",
        name: "dashboard-tree.tsx",
      },
    ],
  },
];
export default function TreeViewExample() {
  return (
    <TreeView
      elements={elements}
      initialSelectedId="3"
      initialExpendedItems={["1", "2"]}
    />
  );
}

This section will be focusing on how to build a custom tree view using the tree-view-api component.

Project Structure

This is the Structure that we will follow for this example

components
└── table_of_content
    ├── tree-item.tsx
    ├── tree.tsx
    ├── toc.tsx

Pre requisites

In the following example we're going to use this elements array :

const elements: TreeViewElement[] = [
  {
    id: "1",
    name: "components",
    children: [
      {
        id: "2",
        name: "extension",
        children: [
          {
            id: "3",
            name: "tree-view.tsx",
          },
          {
            id: "4",
            name: "tree-view-api.tsx",
          },
        ],
      },
      {
        id: "5",
        name: "dashboard-tree.tsx",
      },
    ],
  },
  {
    id: "6",
    name: "pages",
    children: [
      {
        id: "7",
        name: "page.tsx",
      },
      {
        id: "8",
        name: "page-guide.tsx",
      },
    ],
  },
  {
    id: "18",
    name: "env.ts",
  },
];

TOC

TOC component

First we create the TOC component.

components/Table_of_content/tree.tsx
import {
  Tree,
  TreeViewElement,
  CollapseButton,
} from "@/components/extension/tree-view-api";
import { TreeItem } from "./tree-item.tsx";
 
type TOCProps = {
  toc: TreeViewElement[];
};
 
export const TOC = ({ toc }: TOCProps) => {
  return (
    <Tree className="w-full h-60 bg-background p-2 rounded-md" indicator={true}>
      {toc.map((element, _) => (
        <TreeItem key={element.id} elements={[element]} />
      ))}
      <CollapseButton elements={toc} expandAll={true} />
    </Tree>
  );
};

Here we can see that we created a TOC component that will be responsible for rendering all the tree , the CollapseButton is used for the expandAll functionality.

TreeItem component

Then the TreeItem element.

components/Table_of_content/tree-item.tsx
import {
  File,
  Folder,
  TreeViewElement,
} from "@/components/extension/tree-view-api";
 
type TreeItemProps = {
  elements: TreeViewElement[];
};
 
export const TreeItem = ({ elements }: TreeItemProps) => {
  return (
    <ul className="w-full space-y-1">
      {elements.map((element) => (
        <li key={element.id} className="w-full space-y-2">
          {element.children && element.children?.length > 0 ? (
            <Folder
              element={element.name}
              value={element.id}
              isSelectable={element.isSelectable}
              className="px-px pr-1"
            >
              <TreeItem
                key={element.id}
                aria-label={`folder ${element.name}`}
                elements={element.children}
              />
            </Folder>
          ) : (
            <File
              key={element.id}
              value={element.id}
              element={element.name}
              isSelectable={element.isSelectable}
              className={"px-1"}
            >
              <span className="ml-1">{element?.name}</span>
            </File>
          )}
        </li>
      ))}
    </ul>
  );
};

Now let's break the code above :

  • We have created a TreeItem component that will be responsible for rendering the elements of the tree view.
import { File , Folder , TreeViewElement } from "@/components/extension/tree-view-api";
 
type TreeItemProps = {
  elements : TreeViewElement[]
};
 
export const TreeItem = ({ elements } : TreeItemProps) => {
 
return (
   <ul className="w-full space-y-1" >
      {elements.map((element) => (
        <li key={element.id} className="w-full space-y-2">
         {...}
        </li>
        ))
      }
   </ul>
 )
}
 
  • First we check if the element has children or not , if it has children we render a Folder component , otherwise we render a File component.
{
  element.children && element.children?.length > 0 ? (
    <Folder
      element={element.name}
      value={element.id}
      isSelectable={element.isSelectable}
      className="px-px pr-1"
    >
      <TreeItem
        key={element.id}
        aria-label={`folder ${element.name}`}
        elements={element.children}
      />
    </Folder>
  ) : (
    <File
      key={element.id}
      value={element.id}
      element={element.name}
      isSelectable={element.isSelectable}
      className={"px-1"}
    >
      <span className="ml-1">{element?.name}</span>
    </File>
  );
}
  • In case of the Folder component we're calling the TreeItem component again to render the children of the folder.

But wait, as you might notice we didn't loop over the elements as usual (using loop only), rather we are recalling the TreeItem , this called recursion rendering. And we use it because we don't know how many levels of children we have in the tree view , and this is the most efficient way to render the tree view.

Final Step

Now we can use the create the main component TOC that will contain all of this logic, and we will pass the elements to it.

components/Table_of_content/toc.tsx
import { Tree, TreeViewElement } from "@/components/extension/tree-view-api";
 
const TOCWrapper = () => {
  return <TOC element={elements} />;
};
 
export default TOCWrapper;

Final Result

So the end result is going to be like this :