{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tree-table-reorder",
  "title": "Tree Table — Reorder",
  "description": "Drag-handle row reordering scoped to siblings within the same parent, built on @dnd-kit/sortable.",
  "dependencies": [
    "@tanstack/react-table",
    "@dnd-kit/core",
    "@dnd-kit/sortable",
    "@dnd-kit/utilities",
    "@dnd-kit/modifiers"
  ],
  "registryDependencies": [
    "table"
  ],
  "files": [
    {
      "path": "src/components/tree-reorder/columns.tsx",
      "content": "'use client'\n\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { ChevronRight } from 'lucide-react'\n\nexport type OrgNode = {\n  id: string\n  name: string\n  role: string\n  status: string\n  children?: OrgNode[]\n}\n\nexport const columns: ColumnDef<OrgNode>[] = [\n  {\n    accessorKey: 'name',\n    header: 'Name',\n    cell: ({ row }) => (\n      <div\n        className=\"flex items-center gap-1.5\"\n        style={{ paddingLeft: `${row.depth * 1.25}rem` }}\n      >\n        {row.getCanExpand() ? (\n          <button\n            type=\"button\"\n            onClick={row.getToggleExpandedHandler()}\n            className=\"flex h-4 w-4 shrink-0 items-center justify-center text-muted-foreground\"\n            aria-label={row.getIsExpanded() ? 'Collapse row' : 'Expand row'}\n          >\n            <ChevronRight\n              className={`h-3.5 w-3.5 transition-transform ${\n                row.getIsExpanded() ? 'rotate-90' : ''\n              }`}\n            />\n          </button>\n        ) : (\n          <span className=\"w-4 shrink-0\" />\n        )}\n        {row.original.name}\n      </div>\n    ),\n  },\n  {\n    accessorKey: 'role',\n    header: 'Role',\n  },\n  {\n    accessorKey: 'status',\n    header: 'Status',\n  },\n]\n",
      "type": "registry:component",
      "target": "@components/tables/tree-table-reorder/columns.tsx"
    },
    {
      "path": "src/components/tree-reorder/data-table.tsx",
      "content": "'use client'\n\nimport { useMemo, useState } from 'react'\nimport {\n  DndContext,\n  KeyboardSensor,\n  PointerSensor,\n  closestCenter,\n  useSensor,\n  useSensors,\n} from '@dnd-kit/core'\nimport type { DragEndEvent } from '@dnd-kit/core'\nimport { restrictToVerticalAxis } from '@dnd-kit/modifiers'\nimport {\n  SortableContext,\n  arrayMove,\n  sortableKeyboardCoordinates,\n  useSortable,\n  verticalListSortingStrategy,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport {\n  flexRender,\n  getCoreRowModel,\n  getExpandedRowModel,\n  useReactTable,\n} from '@tanstack/react-table'\nimport type { Cell, ColumnDef, ExpandedState, Row } from '@tanstack/react-table'\nimport { GripVertical } from 'lucide-react'\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '#/components/ui/table'\n\ntype TreeNode = { id: string; children?: TreeNode[] }\n\nfunction reorderSiblings<TData extends TreeNode>(\n  nodes: TData[],\n  activeId: string,\n  overId: string,\n): { nodes: TData[]; moved: boolean } {\n  const oldIndex = nodes.findIndex((n) => n.id === activeId)\n  const newIndex = nodes.findIndex((n) => n.id === overId)\n\n  if (oldIndex !== -1 && newIndex !== -1) {\n    return { nodes: arrayMove(nodes, oldIndex, newIndex), moved: true }\n  }\n\n  let moved = false\n  const next = nodes.map((node) => {\n    if (!node.children) return node\n    const result = reorderSiblings(node.children as TData[], activeId, overId)\n    if (result.moved) moved = true\n    return result.moved ? { ...node, children: result.nodes } : node\n  })\n\n  return { nodes: moved ? next : nodes, moved }\n}\n\ninterface TreeReorderDataTableProps<TData extends TreeNode> {\n  columns: ColumnDef<TData, any>[]\n  data: TData[]\n  onDataChange: (data: TData[]) => void\n}\n\nfunction DraggableRow<TData extends TreeNode>({ row }: { row: Row<TData> }) {\n  const { transform, transition, setNodeRef, isDragging, attributes, listeners } =\n    useSortable({ id: row.original.id })\n\n  return (\n    <TableRow\n      ref={setNodeRef}\n      style={{\n        transform: CSS.Translate.toString(transform),\n        transition,\n        opacity: isDragging ? 0.6 : 1,\n        position: 'relative',\n        zIndex: isDragging ? 1 : 0,\n      }}\n    >\n      <TableCell className=\"w-8\">\n        <button\n          type=\"button\"\n          {...attributes}\n          {...listeners}\n          className=\"cursor-grab text-muted-foreground active:cursor-grabbing\"\n          aria-label=\"Reorder row\"\n        >\n          <GripVertical className=\"h-3.5 w-3.5\" />\n        </button>\n      </TableCell>\n      {row.getVisibleCells().map((cell: Cell<TData, unknown>) => (\n        <TableCell key={cell.id}>\n          {flexRender(cell.column.columnDef.cell, cell.getContext())}\n        </TableCell>\n      ))}\n    </TableRow>\n  )\n}\n\nexport function TreeReorderDataTable<TData extends TreeNode>({\n  columns,\n  data,\n  onDataChange,\n}: TreeReorderDataTableProps<TData>) {\n  const [expanded, setExpanded] = useState<ExpandedState>(true)\n\n  const table = useReactTable({\n    data,\n    columns,\n    getRowId: (row) => row.id,\n    getCoreRowModel: getCoreRowModel(),\n    getSubRows: (row) => row.children,\n    getExpandedRowModel: getExpandedRowModel(),\n    onExpandedChange: setExpanded,\n    state: { expanded },\n  })\n\n  const rowIds = useMemo(\n    () => table.getRowModel().rows.map((row) => row.id),\n    [table, data, expanded],\n  )\n\n  const sensors = useSensors(\n    useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    }),\n  )\n\n  function handleDragEnd(event: DragEndEvent) {\n    const { active, over } = event\n    if (!over || active.id === over.id) return\n    const result = reorderSiblings(data, active.id as string, over.id as string)\n    if (result.moved) onDataChange(result.nodes)\n  }\n\n  return (\n    <div className=\"overflow-hidden rounded-md border\">\n      <DndContext\n        sensors={sensors}\n        collisionDetection={closestCenter}\n        modifiers={[restrictToVerticalAxis]}\n        onDragEnd={handleDragEnd}\n      >\n        <Table>\n          <TableHeader>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id}>\n                <TableHead className=\"w-8\" />\n                {headerGroup.headers.map((header) => (\n                  <TableHead key={header.id}>\n                    {header.isPlaceholder\n                      ? null\n                      : flexRender(\n                          header.column.columnDef.header,\n                          header.getContext(),\n                        )}\n                  </TableHead>\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody>\n            {table.getRowModel().rows.length ? (\n              <SortableContext\n                items={rowIds}\n                strategy={verticalListSortingStrategy}\n              >\n                {table.getRowModel().rows.map((row) => (\n                  <DraggableRow key={row.id} row={row} />\n                ))}\n              </SortableContext>\n            ) : (\n              <TableRow>\n                <TableCell\n                  colSpan={columns.length + 1}\n                  className=\"h-24 text-center\"\n                >\n                  No results.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </DndContext>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/tables/tree-table-reorder/data-table.tsx"
    },
    {
      "path": "src/components/tree-reorder/index.tsx",
      "content": "import { useState } from 'react'\n\nimport { columns } from './columns'\nimport type { OrgNode } from './columns'\nimport { TreeReorderDataTable } from './data-table'\n\nconst initialData: OrgNode[] = [\n  {\n    id: 'engineering',\n    name: 'Engineering',\n    role: 'Department',\n    status: '—',\n    children: [\n      {\n        id: 'frontend',\n        name: 'Frontend',\n        role: 'Team',\n        status: '—',\n        children: [\n          {\n            id: 'ava',\n            name: 'Ava Thompson',\n            role: 'Frontend Lead',\n            status: 'Active',\n          },\n          {\n            id: 'liam',\n            name: 'Liam Chen',\n            role: 'Frontend Engineer',\n            status: 'Active',\n          },\n        ],\n      },\n      {\n        id: 'backend',\n        name: 'Backend',\n        role: 'Team',\n        status: '—',\n        children: [\n          {\n            id: 'sofia',\n            name: 'Sofia Patel',\n            role: 'Backend Lead',\n            status: 'Active',\n          },\n          {\n            id: 'noah',\n            name: 'Noah Garcia',\n            role: 'Backend Engineer',\n            status: 'Inactive',\n          },\n        ],\n      },\n    ],\n  },\n  {\n    id: 'design',\n    name: 'Design',\n    role: 'Department',\n    status: '—',\n    children: [\n      {\n        id: 'product-design',\n        name: 'Product Design',\n        role: 'Team',\n        status: '—',\n        children: [\n          {\n            id: 'mia',\n            name: 'Mia Johnson',\n            role: 'Design Lead',\n            status: 'Active',\n          },\n          {\n            id: 'ethan',\n            name: 'Ethan Kim',\n            role: 'Product Designer',\n            status: 'Pending',\n          },\n        ],\n      },\n    ],\n  },\n]\n\nexport function TreeReorderDemo() {\n  const [data, setData] = useState(initialData)\n\n  return (\n    <TreeReorderDataTable columns={columns} data={data} onDataChange={setData} />\n  )\n}\n",
      "type": "registry:component",
      "target": "@components/tables/tree-table-reorder/index.tsx"
    }
  ],
  "type": "registry:block"
}