Tree Drag

PreviousNext

tree-drag-demo

Docs
intentuipage

Preview

Loading preview…
components/docs/collections/tree/tree-drag-demo.tsx
"use client"

import {
  DocumentIcon,
  DocumentTextIcon,
  FolderIcon,
  FolderOpenIcon,
  PlayCircleIcon,
  Squares2X2Icon,
} from "@heroicons/react/24/outline"
import { useTreeData } from "@react-stately/data"
import {
  Collection,
  type TreeItemProps as TreeItemPrimitiveProps,
  useDragAndDrop,
} from "react-aria-components"
import { TreeItem as PrimitiveItem, Tree, TreeContent } from "@/components/ui/tree"

const fileStructure = [
  {
    id: "departments",
    name: "departments",
    children: [
      {
        id: "sales",
        name: "sales",
        children: [
          {
            id: "reports",
            name: "reports",
            children: [
              { id: "q1", name: "Q1.pdf" },
              { id: "q2", name: "Q2.pdf" },
              { id: "q3", name: "Q3.pdf" },
            ],
          },
          {
            id: "s-clients",
            name: "s-clients",
            children: [
              { id: "europe", name: "europe.xlsx" },
              { id: "asia", name: "asia.xlsx" },
            ],
          },
        ],
      },
      {
        id: "hr",
        name: "hr",
        children: [
          {
            id: "d-docs",
            name: "d-docs",
            children: [
              { id: "policy", name: "leave-policy.pdf" },
              { id: "handbook", name: "handbook.pdf" },
              { id: "benefits", name: "benefits.pdf" },
            ],
          },
        ],
      },
    ],
  },
  {
    id: "projects",
    name: "projects",
    children: [
      {
        id: "revamp",
        name: "website-revamp",
        children: [
          {
            id: "designs",
            name: "designs",
            children: [
              { id: "home", name: "home.fig" },
              { id: "about", name: "about.fig" },
              { id: "pricing", name: "pricing.fig" },
            ],
          },
        ],
      },
      {
        id: "mobile",
        name: "mobile-app",
        children: [
          {
            id: "ios",
            name: "ios",
            children: [
              { id: "v1", name: "v1.0.ipa" },
              { id: "v2", name: "v2.0.ipa" },
            ],
          },
        ],
      },
    ],
  },
  {
    id: "clients",
    name: "clients",
    children: [
      {
        id: "acme",
        name: "acme-corp",
        children: [
          {
            id: "contracts",
            name: "contracts",
            children: [
              { id: "c-2024", name: "2024.pdf" },
              { id: "c-2025", name: "2025.pdf" },
            ],
          },
        ],
      },
      {
        id: "initech",
        name: "initech",
        children: [
          {
            id: "invoices",
            name: "invoices",
            children: [
              { id: "inv1", name: "inv-001.pdf" },
              { id: "inv2", name: "inv-002.pdf" },
              { id: "inv3", name: "inv-003.pdf" },
            ],
          },
        ],
      },
    ],
  },
  {
    id: "legal",
    name: "legal",
    children: [
      {
        id: "nda",
        name: "nda",
        children: [
          { id: "partner-a", name: "partner-a.pdf" },
          { id: "partner-b", name: "partner-b.pdf" },
        ],
      },
      {
        id: "terms",
        name: "terms",
        children: [
          { id: "t-2024", name: "2024.pdf" },
          { id: "t-2025", name: "2025.pdf" },
        ],
      },
    ],
  },
  {
    id: "resources",
    name: "resources",
    children: [
      {
        id: "docs",
        name: "docs",
        children: [
          { id: "api", name: "api.md" },
          { id: "auth", name: "auth.md" },
          { id: "sdk", name: "sdk.md" },
        ],
      },
      {
        id: "videos",
        name: "videos",
        children: [
          { id: "intro", name: "intro.mp4" },
          { id: "setup", name: "setup.mp4" },
        ],
      },
    ],
  },
]

interface TreeItemProps extends Partial<TreeItemPrimitiveProps> {
  title: string
}

function TreeItem({ title, children, ...props }: TreeItemProps) {
  return (
    <PrimitiveItem textValue={title} {...props}>
      <TreeContent>
        {({ isExpanded, hasChildItems }) => (
          <>
            {hasChildItems ? (
              isExpanded ? (
                <FolderOpenIcon />
              ) : (
                <FolderIcon />
              )
            ) : title.endsWith(".fig") ? (
              <Squares2X2Icon />
            ) : title.endsWith(".mp4") ? (
              <PlayCircleIcon />
            ) : title.endsWith(".pdf") ? (
              <DocumentIcon />
            ) : (
              <DocumentTextIcon />
            )}
            {title}
          </>
        )}
      </TreeContent>
      {children}
    </PrimitiveItem>
  )
}

export default function TreeDragDemo() {
  const tree = useTreeData({
    initialItems: fileStructure,
  })

  const { dragAndDropHooks } = useDragAndDrop({
    getItems: (keys) =>
      [...keys].map((key) => ({
        "text/plain": (tree.getItem(key) as any).value.name,
      })),
    onMove(e) {
      if (e.target.dropPosition === "before") {
        tree.moveBefore(e.target.key, e.keys)
      } else if (e.target.dropPosition === "after") {
        tree.moveAfter(e.target.key, e.keys)
      } else if (e.target.dropPosition === "on") {
        const targetNode = tree.getItem(e.target.key)
        if (targetNode) {
          const targetIndex = targetNode.children ? targetNode.children.length : 0
          const keyArray = Array.from(e.keys)
          for (let i = 0; i < keyArray.length; i++) {
            tree.move(keyArray[i], e.target.key, targetIndex + i)
          }
        }
      }
    },
  })

  return (
    <Tree aria-label="File system" items={tree.items} dragAndDropHooks={dragAndDropHooks}>
      {function renderItem(item) {
        return (
          <TreeItem title={(item as any).value.name}>
            <Collection items={(item as any).children ?? []}>{renderItem}</Collection>
          </TreeItem>
        )
      }}
    </Tree>
  )
}

Installation

npx shadcn@latest add @intentui/tree-drag-demo

Usage

Usage varies by registry entry. Refer to the registry docs or source files below for details.