Shadcn Treeview
Examples

Context Menu

Tree view with right-click context menu and inline editing

This example shows how to add a context menu with rename functionality. Right-click on any item to see the context menu.

Code

import * as React from "react";
import { TreeView, TreeViewItem, flattenTree } from "shadcn-treeview";
import { FileIcon, FolderIcon, FolderOpenIcon } from "lucide-react";
import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuTrigger
} from "@/components/ui/context-menu";

export function ContextMenuTreeView() {
  const [data, setData] = React.useState({
    id: "root",
    name: "Project",
    children: [
      {
        id: "1",
        name: "src",
        children: [
          {
            id: "11",
            name: "components",
            children: [{ id: "111", name: "tree-view.tsx" }]
          },
          { id: "12", name: "app.tsx" },
          { id: "13", name: "index.tsx" }
        ]
      },
      { id: "2", name: "package.json" },
      { id: "3", name: "README.md" }
    ]
  });

  const handleEditMode = (id: string) => {
    setData((prevData) => {
      const updateNode = (node: any): any => {
        if (node.id === id) {
          return { ...node, metadata: { isEditing: true } };
        }
        if (node.children) {
          return { ...node, children: node.children.map(updateNode) };
        }
        return node;
      };
      return updateNode(prevData);
    });
  };

  const handleEditSubmit = (id: string, newName: string) => {
    setData((prevData) => {
      const updateNode = (node: any): any => {
        if (node.id === id) {
          return { ...node, name: newName, metadata: { isEditing: false } };
        }
        if (node.children) {
          return { ...node, children: node.children.map(updateNode) };
        }
        return node;
      };
      return updateNode(prevData);
    });
  };

  return (
    <TreeView
      data={flattenTree(data)}
      aria-label="Context menu tree"
      className="w-64 rounded border p-2"
      nodeRenderer={({
        element,
        isBranch,
        isExpanded,
        isSelected,
        getNodeProps,
        level
      }) => (
        <ContextMenu>
          <ContextMenuTrigger asChild>
            <TreeViewItem
              {...getNodeProps()}
              name={element.name}
              isBranch={isBranch}
              isExpanded={isExpanded}
              isSelected={isSelected}
              level={level}
              indentation={16}
              isEditing={element.metadata?.isEditing === true}
              onEditSubmit={(value) => handleEditSubmit(element.id.toString(), value)}
              icon={
                isBranch ? (
                  isExpanded ? (
                    <FolderOpenIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
                  ) : (
                    <FolderIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
                  )
                ) : (
                  <FileIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
                )
              }
            />
          </ContextMenuTrigger>
          <ContextMenuContent>
            <ContextMenuItem onSelect={() => handleEditMode(element.id.toString())}>
              Rename
            </ContextMenuItem>
            <ContextMenuSeparator />
            <ContextMenuItem disabled>Cut</ContextMenuItem>
            <ContextMenuItem disabled>Copy</ContextMenuItem>
            <ContextMenuSeparator />
            <ContextMenuItem disabled className="text-destructive">
              Delete
            </ContextMenuItem>
          </ContextMenuContent>
        </ContextMenu>
      )}
    />
  );
}

Inline Editing Behavior

When isEditing is true:

  1. The name text is hidden and replaced with an input
  2. The input is auto-focused
  3. Press Enter or click outside to submit
  4. Press Escape to cancel and restore the original name
  5. onEditSubmit is called with the new value

On this page