{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"tree-nav","type":"registry:ui","title":"TreeNav","description":"TreeNav from react-fancy","package":"react-fancy","dependencies":[],"registryDependencies":[],"files":[{"path":"components\/fancy\/tree-nav\/TreeNav.context.ts","content":"import { createContext, useContext } from \"react\";\r\nimport type { TreeNavContextValue } from \".\/TreeNav.types\";\r\n\r\nexport const TreeNavContext = createContext<TreeNavContextValue | null>(null);\r\n\r\nexport function useTreeNav(): TreeNavContextValue {\r\n  const ctx = useContext(TreeNavContext);\r\n  if (!ctx) {\r\n    throw new Error(\"useTreeNav must be used within a <TreeNav> component\");\r\n  }\r\n  return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/tree-nav\/TreeNav.context.ts"},{"path":"components\/fancy\/tree-nav\/TreeNav.tsx","content":"import { useMemo, useCallback, useState } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { TreeNavContext } from \".\/TreeNav.context\";\r\nimport { TreeNode } from \".\/TreeNode\";\r\nimport type { TreeNavProps, TreeNodeData, DragState } from \".\/TreeNav.types\";\r\n\r\nfunction collectFolderIds(nodes: TreeNodeData[]): string[] {\r\n  const ids: string[] = [];\r\n  for (const node of nodes) {\r\n    if (node.children && node.children.length > 0) {\r\n      ids.push(node.id);\r\n      ids.push(...collectFolderIds(node.children));\r\n    }\r\n  }\r\n  return ids;\r\n}\r\n\r\nconst EMPTY_DRAG_STATE: DragState = {\r\n  draggedNodeId: null,\r\n  dropTargetId: null,\r\n  dropPosition: null,\r\n};\r\n\r\nfunction TreeNavRoot({\r\n  nodes,\r\n  selectedId,\r\n  onSelect,\r\n  onNodeContextMenu,\r\n  draggable = false,\r\n  onNodeMove,\r\n  acceptExternalDrops = false,\r\n  onExternalDrop,\r\n  expandedIds: controlledExpanded,\r\n  defaultExpandedIds,\r\n  onExpandedChange,\r\n  defaultExpandAll = false,\r\n  indentSize = 16,\r\n  showIcons = true,\r\n  dragCursor = \"none\",\r\n  className,\r\n}: TreeNavProps) {\r\n  const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {\r\n    if (defaultExpandedIds) return defaultExpandedIds;\r\n    if (defaultExpandAll) return collectFolderIds(nodes);\r\n    return [];\r\n  });\r\n\r\n  const isControlled = controlledExpanded !== undefined;\r\n  const expandedIds = isControlled ? controlledExpanded : internalExpanded;\r\n\r\n  const toggle = useCallback(\r\n    (id: string) => {\r\n      const next = expandedIds.includes(id)\r\n        ? expandedIds.filter((v) => v !== id)\r\n        : [...expandedIds, id];\r\n\r\n      if (!isControlled) {\r\n        setInternalExpanded(next);\r\n      }\r\n      onExpandedChange?.(next);\r\n    },\r\n    [expandedIds, isControlled, onExpandedChange],\r\n  );\r\n\r\n  const expandNode = useCallback(\r\n    (id: string) => {\r\n      if (expandedIds.includes(id)) return;\r\n      const next = [...expandedIds, id];\r\n      if (!isControlled) {\r\n        setInternalExpanded(next);\r\n      }\r\n      onExpandedChange?.(next);\r\n    },\r\n    [expandedIds, isControlled, onExpandedChange],\r\n  );\r\n\r\n  const [dragState, setDragState] = useState<DragState>(EMPTY_DRAG_STATE);\r\n\r\n  const handleDragEnd = useCallback(() => {\r\n    setDragState(EMPTY_DRAG_STATE);\r\n  }, []);\r\n\r\n  const ctx = useMemo(\r\n    () => ({\r\n      selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\r\n      draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\r\n    }),\r\n    [selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\r\n     draggable, dragCursor, dragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode],\r\n  );\r\n\r\n  const dropEnabled = draggable || acceptExternalDrops;\r\n\r\n  return (\r\n    <TreeNavContext.Provider value={ctx}>\r\n      <nav\r\n        data-react-fancy-tree-nav=\"\"\r\n        className={cn(\"flex flex-col gap-0.5 py-1 text-sm\", className)}\r\n        onDragEnd={dropEnabled ? handleDragEnd : undefined}\r\n      >\r\n        {nodes.map((node) => (\r\n          <TreeNode key={node.id} node={node} depth={0} \/>\r\n        ))}\r\n      <\/nav>\r\n    <\/TreeNavContext.Provider>\r\n  );\r\n}\r\n\r\nTreeNavRoot.displayName = \"TreeNav\";\r\n\r\nexport const TreeNav = Object.assign(TreeNavRoot, {\r\n  Node: TreeNode,\r\n});\r\n","type":"registry:ui","target":"components\/fancy\/tree-nav\/TreeNav.tsx"},{"path":"components\/fancy\/tree-nav\/TreeNav.types.ts","content":"import type { ReactNode } from \"react\";\r\n\r\nexport interface TreeNodeData {\r\n  \/** Unique identifier *\/\r\n  id: string;\r\n  \/** Display label *\/\r\n  label: string;\r\n  \/** Node type for icon rendering *\/\r\n  type?: \"file\" | \"folder\";\r\n  \/** File extension hint (e.g., \"ts\", \"php\", \"html\") for icon coloring *\/\r\n  ext?: string;\r\n  \/** Nested children *\/\r\n  children?: TreeNodeData[];\r\n  \/** Custom icon override *\/\r\n  icon?: ReactNode;\r\n  \/** Whether the node is disabled *\/\r\n  disabled?: boolean;\r\n}\r\n\r\n\/** Where the dragged node will land relative to the drop target *\/\r\nexport type DropPosition = \"before\" | \"after\" | \"inside\";\r\n\r\nexport interface DragState {\r\n  draggedNodeId: string | null;\r\n  dropTargetId: string | null;\r\n  dropPosition: DropPosition | null;\r\n}\r\n\r\nexport interface TreeNavProps {\r\n  \/** Tree data *\/\r\n  nodes: TreeNodeData[];\r\n  \/** Currently selected node ID *\/\r\n  selectedId?: string;\r\n  \/** Callback when a node is selected *\/\r\n  onSelect?: (id: string, node: TreeNodeData) => void;\r\n  \/** Callback when a node is right-clicked *\/\r\n  onNodeContextMenu?: (e: React.MouseEvent, node: TreeNodeData) => void;\r\n  \/** Enable drag-and-drop reordering (default: false) *\/\r\n  draggable?: boolean;\r\n  \/** Callback when a node is moved via drag and drop *\/\r\n  onNodeMove?: (sourceId: string, targetId: string, position: DropPosition) => void;\r\n  \/** Accept drops from outside the tree \u2014 OS files, items from other\r\n   *  components, etc. When true, drag-over is allowed for any data source\r\n   *  and `onExternalDrop` fires on drop. (default: false) *\/\r\n  acceptExternalDrops?: boolean;\r\n  \/** Called when an external drag (file or cross-component) is dropped on\r\n   *  a node. The raw event lets you read `event.dataTransfer.files` for OS\r\n   *  file drops or `event.dataTransfer.getData(type)` for custom MIME\r\n   *  payloads from other components. *\/\r\n  onExternalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;\r\n  \/** Controlled expanded node IDs *\/\r\n  expandedIds?: string[];\r\n  \/** Default expanded node IDs (uncontrolled) *\/\r\n  defaultExpandedIds?: string[];\r\n  \/** Callback when expanded state changes *\/\r\n  onExpandedChange?: (ids: string[]) => void;\r\n  \/** Expand all folders by default *\/\r\n  defaultExpandAll?: boolean;\r\n  \/** Indent size per nesting level in px (default: 16) *\/\r\n  indentSize?: number;\r\n  \/** Show file\/folder icons (default: true) *\/\r\n  showIcons?: boolean;\r\n  \/**\r\n   * Cursor shown on hover over a draggable node.\r\n   * - `\"none\"` (default): the normal clickable cursor \u2014 no move affordance on\r\n   *   mere hover. A closed-hand `grabbing` cursor still appears while a drag is\r\n   *   actually happening. This suits click-to-select\/open trees (file\/nav\r\n   *   trees), where an always-on grab cursor wrongly implies move-first intent.\r\n   * - `\"grab\"`: show the open-hand `grab` cursor on hover. Opt in when the whole\r\n   *   row is genuinely a drag handle and reordering is the primary interaction.\r\n   * @default \"none\"\r\n   *\/\r\n  dragCursor?: \"grab\" | \"none\";\r\n  \/** Custom className *\/\r\n  className?: string;\r\n}\r\n\r\nexport interface TreeNavContextValue {\r\n  selectedId?: string;\r\n  onSelect?: (id: string, node: TreeNodeData) => void;\r\n  onNodeContextMenu?: (e: React.MouseEvent, node: TreeNodeData) => void;\r\n  expandedIds: string[];\r\n  toggle: (id: string) => void;\r\n  indentSize: number;\r\n  showIcons: boolean;\r\n  draggable: boolean;\r\n  dragCursor: \"grab\" | \"none\";\r\n  dragState: DragState;\r\n  setDragState: (state: DragState) => void;\r\n  onNodeMove?: (sourceId: string, targetId: string, position: DropPosition) => void;\r\n  acceptExternalDrops: boolean;\r\n  onExternalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;\r\n  nodes: TreeNodeData[];\r\n  expandNode: (id: string) => void;\r\n}\r\n\r\nexport interface TreeNodeProps {\r\n  node: TreeNodeData;\r\n  depth: number;\r\n  className?: string;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/tree-nav\/TreeNav.types.ts"},{"path":"components\/fancy\/tree-nav\/TreeNode.tsx","content":"import { useRef, useEffect, useCallback } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useTreeNav } from \".\/TreeNav.context\";\r\nimport type { TreeNodeProps, TreeNodeData, DropPosition } from \".\/TreeNav.types\";\r\n\r\nconst EXT_COLORS: Record<string, string> = {\r\n  ts: \"#3178c6\",\r\n  tsx: \"#3178c6\",\r\n  js: \"#f7df1e\",\r\n  jsx: \"#f7df1e\",\r\n  php: \"#777bb4\",\r\n  html: \"#e34c26\",\r\n  htm: \"#e34c26\",\r\n  css: \"#264de4\",\r\n  json: \"#a1a1aa\",\r\n  md: \"#71717a\",\r\n  yaml: \"#cb171e\",\r\n  yml: \"#cb171e\",\r\n};\r\n\r\nfunction FileIcon({ ext }: { ext?: string }) {\r\n  const color = (ext && EXT_COLORS[ext.toLowerCase()]) || \"#71717a\";\r\n  return (\r\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\r\n      <path d=\"M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z\" stroke={color} strokeWidth=\"1.2\" \/>\r\n      <path d=\"M9 1v4h4\" stroke={color} strokeWidth=\"1.2\" \/>\r\n    <\/svg>\r\n  );\r\n}\r\n\r\nfunction FolderIcon({ open }: { open: boolean }) {\r\n  if (open) {\r\n    return (\r\n      <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\r\n        <path d=\"M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z\" fill=\"#fbbf24\" \/>\r\n        <path d=\"M1 6h13l-1.5 7.5H2.5L1 6z\" fill=\"#fbbf24\" opacity=\"0.7\" \/>\r\n      <\/svg>\r\n    );\r\n  }\r\n  return (\r\n    <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\r\n      <path d=\"M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z\" fill=\"#fbbf24\" \/>\r\n    <\/svg>\r\n  );\r\n}\r\n\r\nfunction ChevronIcon({ open }: { open: boolean }) {\r\n  return (\r\n    <svg\r\n      width=\"14\"\r\n      height=\"14\"\r\n      viewBox=\"0 0 24 24\"\r\n      fill=\"none\"\r\n      stroke=\"currentColor\"\r\n      strokeWidth=\"2\"\r\n      strokeLinecap=\"round\"\r\n      strokeLinejoin=\"round\"\r\n      className={cn(\"shrink-0 transition-transform duration-150\", open && \"rotate-90\")}\r\n    >\r\n      <polyline points=\"9 18 15 12 9 6\" \/>\r\n    <\/svg>\r\n  );\r\n}\r\n\r\n\/\/ ---------------------------------------------------------------------------\r\n\/\/ Utilities\r\n\/\/ ---------------------------------------------------------------------------\r\n\r\nfunction isDescendantOf(\r\n  nodes: TreeNodeData[],\r\n  ancestorId: string,\r\n  targetId: string,\r\n): boolean {\r\n  function findNode(haystack: TreeNodeData[], id: string): TreeNodeData | undefined {\r\n    for (const n of haystack) {\r\n      if (n.id === id) return n;\r\n      if (n.children) {\r\n        const found = findNode(n.children, id);\r\n        if (found) return found;\r\n      }\r\n    }\r\n    return undefined;\r\n  }\r\n\r\n  function hasDescendant(node: TreeNodeData, id: string): boolean {\r\n    if (!node.children) return false;\r\n    for (const child of node.children) {\r\n      if (child.id === id) return true;\r\n      if (hasDescendant(child, id)) return true;\r\n    }\r\n    return false;\r\n  }\r\n\r\n  const ancestor = findNode(nodes, ancestorId);\r\n  return ancestor ? hasDescendant(ancestor, targetId) : false;\r\n}\r\n\r\nfunction computeDropPosition(\r\n  e: React.DragEvent,\r\n  isFolder: boolean,\r\n): DropPosition {\r\n  const rect = e.currentTarget.getBoundingClientRect();\r\n  const offsetY = e.clientY - rect.top;\r\n  const third = rect.height \/ 3;\r\n\r\n  if (offsetY < third) return \"before\";\r\n  if (offsetY > third * 2) return \"after\";\r\n  return isFolder ? \"inside\" : \"after\";\r\n}\r\n\r\n\/\/ ---------------------------------------------------------------------------\r\n\/\/ Component\r\n\/\/ ---------------------------------------------------------------------------\r\n\r\nexport function TreeNode({ node, depth }: TreeNodeProps) {\r\n  const {\r\n    selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\r\n    draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\r\n  } = useTreeNav();\r\n\r\n  const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\r\n  const isExpanded = expandedIds.includes(node.id);\r\n  const isSelected = selectedId === node.id;\r\n  const paddingLeft = depth * indentSize + 4;\r\n\r\n  const isDragging = dragState.draggedNodeId === node.id;\r\n  const isDropTarget = dragState.dropTargetId === node.id;\r\n  const dropPosition = isDropTarget ? dragState.dropPosition : null;\r\n\r\n  \/\/ Auto-expand timer for folders during drag\r\n  const autoExpandTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n  const clearAutoExpand = useCallback(() => {\r\n    if (autoExpandTimer.current) {\r\n      clearTimeout(autoExpandTimer.current);\r\n      autoExpandTimer.current = null;\r\n    }\r\n  }, []);\r\n\r\n  useEffect(() => clearAutoExpand, [clearAutoExpand]);\r\n\r\n  \/\/ ---------------------------------------------------------------------------\r\n  \/\/ Click handlers\r\n  \/\/ ---------------------------------------------------------------------------\r\n\r\n  const handleClick = () => {\r\n    if (node.disabled) return;\r\n    if (isFolder) {\r\n      toggle(node.id);\r\n    }\r\n    onSelect?.(node.id, node);\r\n  };\r\n\r\n  const handleContextMenu = (e: React.MouseEvent) => {\r\n    if (onNodeContextMenu) {\r\n      e.preventDefault();\r\n      onNodeContextMenu(e, node);\r\n    }\r\n  };\r\n\r\n  \/\/ ---------------------------------------------------------------------------\r\n  \/\/ Drag source handlers\r\n  \/\/ ---------------------------------------------------------------------------\r\n\r\n  const handleDragStart = useCallback((e: React.DragEvent) => {\r\n    e.dataTransfer.setData(\"text\/plain\", node.id);\r\n    e.dataTransfer.effectAllowed = \"move\";\r\n    \/\/ Set state async so the browser captures the element for the drag ghost first\r\n    requestAnimationFrame(() => {\r\n      setDragState({ draggedNodeId: node.id, dropTargetId: null, dropPosition: null });\r\n    });\r\n  }, [node.id, setDragState]);\r\n\r\n  const handleDragEnd = useCallback(() => {\r\n    clearAutoExpand();\r\n    setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\r\n  }, [clearAutoExpand, setDragState]);\r\n\r\n  \/\/ ---------------------------------------------------------------------------\r\n  \/\/ Drop target handlers (on outer div)\r\n  \/\/ ---------------------------------------------------------------------------\r\n\r\n  const isExternalDrag = !dragState.draggedNodeId;\r\n\r\n  const handleDragOver = useCallback((e: React.DragEvent) => {\r\n    if (isExternalDrag) {\r\n      \/\/ External drop accepted only when host opted in.\r\n      if (!acceptExternalDrops) return;\r\n    } else {\r\n      const sourceId = dragState.draggedNodeId!;\r\n      if (sourceId === node.id) return;\r\n      if (isDescendantOf(nodes, sourceId, node.id)) return;\r\n    }\r\n\r\n    e.preventDefault();\r\n    e.stopPropagation();\r\n    e.dataTransfer.dropEffect = isExternalDrag ? \"copy\" : \"move\";\r\n\r\n    const position = computeDropPosition(e, !!isFolder);\r\n\r\n    \/\/ Auto-expand collapsed folders when hovering \"inside\"\r\n    if (isFolder && !isExpanded && position === \"inside\") {\r\n      if (!autoExpandTimer.current) {\r\n        autoExpandTimer.current = setTimeout(() => {\r\n          expandNode(node.id);\r\n          autoExpandTimer.current = null;\r\n        }, 500);\r\n      }\r\n    } else {\r\n      clearAutoExpand();\r\n    }\r\n\r\n    if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {\r\n      setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });\r\n    }\r\n  }, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);\r\n\r\n  const handleDragLeave = useCallback((e: React.DragEvent) => {\r\n    if (!e.currentTarget.contains(e.relatedTarget as Node)) {\r\n      clearAutoExpand();\r\n      if (dragState.dropTargetId === node.id) {\r\n        setDragState({ ...dragState, dropTargetId: null, dropPosition: null });\r\n      }\r\n    }\r\n  }, [dragState, node.id, setDragState, clearAutoExpand]);\r\n\r\n  const handleDrop = useCallback((e: React.DragEvent) => {\r\n    e.preventDefault();\r\n    e.stopPropagation();\r\n    clearAutoExpand();\r\n\r\n    const sourceId = dragState.draggedNodeId;\r\n    const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);\r\n\r\n    if (sourceId) {\r\n      \/\/ Internal node move.\r\n      if (sourceId === node.id) return;\r\n      if (isDescendantOf(nodes, sourceId, node.id)) return;\r\n      onNodeMove?.(sourceId, node.id, position);\r\n    } else if (acceptExternalDrops) {\r\n      \/\/ External drop \u2014 files, items from other components, etc.\r\n      onExternalDrop?.(e, node, position);\r\n    }\r\n\r\n    setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\r\n  }, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);\r\n\r\n  \/\/ ---------------------------------------------------------------------------\r\n  \/\/ Render\r\n  \/\/ ---------------------------------------------------------------------------\r\n\r\n  const canDrag = draggable && !node.disabled;\r\n  const dropEnabled = draggable || acceptExternalDrops;\r\n\r\n  return (\r\n    <div\r\n      data-react-fancy-tree-node=\"\"\r\n      onDragOver={dropEnabled ? handleDragOver : undefined}\r\n      onDragLeave={dropEnabled ? handleDragLeave : undefined}\r\n      onDrop={dropEnabled ? handleDrop : undefined}\r\n    >\r\n      {\/* Before drop indicator *\/}\r\n      {isDropTarget && dropPosition === \"before\" && (\r\n        <div\r\n          data-react-fancy-tree-drop-indicator=\"before\"\r\n          className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\r\n          style={{ marginLeft: paddingLeft }}\r\n        \/>\r\n      )}\r\n\r\n      <button\r\n        type=\"button\"\r\n        draggable={canDrag}\r\n        onDragStart={canDrag ? handleDragStart : undefined}\r\n        onDragEnd={canDrag ? handleDragEnd : undefined}\r\n        onClick={handleClick}\r\n        onContextMenu={handleContextMenu}\r\n        disabled={node.disabled}\r\n        className={cn(\r\n          \"flex w-full items-center gap-1 rounded-md py-0.5 text-left text-[13px] transition-colors\",\r\n          isSelected\r\n            ? \"bg-blue-500\/15 text-blue-600 dark:text-blue-400\"\r\n            : \"text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800\",\r\n          node.disabled && \"pointer-events-none opacity-40\",\r\n          isDragging && \"opacity-50\",\r\n          \/\/ Grab affordance. By default (`dragCursor=\"none\"`) the row keeps its\r\n          \/\/ normal click cursor on hover \u2014 no open-hand \"move me\" cursor before\r\n          \/\/ any drag \u2014 and only shows the closed-hand `grabbing` cursor while a\r\n          \/\/ drag is actually underway (mousedown\/drag). `dragCursor=\"grab\"` opts\r\n          \/\/ back into the always-on grab cursor for whole-row drag handles (#14).\r\n          canDrag &&\r\n            (dragCursor === \"grab\" ? \"cursor-grab active:cursor-grabbing\" : \"active:cursor-grabbing\"),\r\n          isDropTarget && dropPosition === \"inside\" && \"bg-blue-500\/10 ring-1 ring-blue-500\/30 ring-inset\",\r\n        )}\r\n        style={{ paddingLeft }}\r\n      >\r\n        {isFolder && <ChevronIcon open={isExpanded} \/>}\r\n        {!isFolder && <span className=\"w-3.5 shrink-0\" \/>}\r\n        {showIcons && (\r\n          node.icon ?? (isFolder\r\n            ? <FolderIcon open={isExpanded} \/>\r\n            : <FileIcon ext={node.ext ?? node.label.split(\".\").pop()} \/>)\r\n        )}\r\n        <span className=\"truncate\">{node.label}<\/span>\r\n      <\/button>\r\n\r\n      {\/* After drop indicator *\/}\r\n      {isDropTarget && dropPosition === \"after\" && (\r\n        <div\r\n          data-react-fancy-tree-drop-indicator=\"after\"\r\n          className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\r\n          style={{ marginLeft: paddingLeft }}\r\n        \/>\r\n      )}\r\n\r\n      {isFolder && isExpanded && node.children && (\r\n        <div data-react-fancy-tree-node-children=\"\">\r\n          {node.children.map((child) => (\r\n            <TreeNode key={child.id} node={child} depth={depth + 1} \/>\r\n          ))}\r\n        <\/div>\r\n      )}\r\n    <\/div>\r\n  );\r\n}\r\n\r\nTreeNode.displayName = \"TreeNode\";\r\n","type":"registry:ui","target":"components\/fancy\/tree-nav\/TreeNode.tsx"},{"path":"components\/fancy\/tree-nav\/index.ts","content":"export { TreeNav } from \".\/TreeNav\";\r\nexport { useTreeNav } from \".\/TreeNav.context\";\r\nexport type {\r\n  TreeNavProps,\r\n  TreeNodeData,\r\n  TreeNodeProps,\r\n  TreeNavContextValue,\r\n  DropPosition,\r\n} from \".\/TreeNav.types\";\r\n","type":"registry:ui","target":"components\/fancy\/tree-nav\/index.ts"}]}