{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tree",
  "title": "Tree",
  "description": "Nested items you can expand, select and drag.",
  "dependencies": [
    "@base-ui/react",
    "@headless-tree/core",
    "@headless-tree/react"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/utils.json",
    "https://ui.ndekocode.dev/r/icons.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/ui/tree.tsx",
      "content": "'use client';\n\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport type {\n  ItemInstance,\n  TreeConfig,\n  TreeInstance,\n} from '@headless-tree/core';\nimport { useTree as useHeadlessTree } from '@headless-tree/react';\nimport { createContext, useContext, useState } from 'react';\n\nimport { ChevronDownIcon } from '@/registry/ndeko/lib/icons';\nimport { cn } from '@/registry/ndeko/lib/utils';\n\ntype TreeContextValue = {\n  indent: number;\n  currentItem?: ItemInstance<unknown>;\n  tree?: TreeInstance<unknown>;\n};\n\nconst TreeContext = createContext<TreeContextValue>({ indent: 20 });\n\nfunction useTreeContext() {\n  return useContext(TreeContext);\n}\n\n/**\n * headless-tree's `useTree`, with the visible rows built during the first render. The library\n * builds them in an effect after mount, so without this the tree is missing from the server HTML\n * and only appears once the page has hydrated.\n */\nfunction useTree<T>(config: TreeConfig<T>) {\n  const tree = useHeadlessTree<T>(config);\n  useState(() => {\n    tree.scheduleRebuildTree();\n    return true;\n  });\n  return tree;\n}\n\ntype TreeProps<T> = React.ComponentProps<'div'> & {\n  indent?: number;\n  tree?: TreeInstance<T>;\n};\n\nfunction containerPropsOf<T>(\n  tree: TreeInstance<T> | undefined,\n  label: string | undefined\n) {\n  const hasContainerProps = typeof tree?.getContainerProps === 'function';\n  if (!tree || !hasContainerProps) {\n    return {};\n  }\n  return tree.getContainerProps(label);\n}\n\nfunction Tree<T>({\n  indent = 20,\n  tree,\n  className,\n  style,\n  'aria-label': label,\n  ...props\n}: TreeProps<T>) {\n  const containerProps = containerPropsOf(tree, label);\n  const treeStyle = {\n    ...style,\n    '--tree-indent': `${indent}px`,\n  } as React.CSSProperties;\n  const ownProps = {\n    'data-slot': 'tree',\n    'aria-label': label,\n    style: treeStyle,\n    className: cn('flex flex-col', className),\n  };\n  const contextTree = tree as TreeInstance<unknown> | undefined;\n\n  return (\n    <TreeContext.Provider value={{ indent, tree: contextTree }}>\n      <div {...mergeProps<'div'>(ownProps, props, containerProps)} />\n    </TreeContext.Provider>\n  );\n}\n\nfunction stateOf(check: (() => boolean) | undefined) {\n  if (typeof check !== 'function') {\n    return undefined;\n  }\n  return check() || false;\n}\n\ntype TreeItemProps<T> = useRender.ComponentProps<'button'> & {\n  item: ItemInstance<T>;\n};\n\nfunction TreeItem<T>({\n  item,\n  className,\n  render,\n  style,\n  children,\n  ...props\n}: TreeItemProps<T>) {\n  const { indent } = useTreeContext();\n  const itemProps = item.getProps();\n  const level = item.getItemMeta().level;\n  const itemStyle = {\n    ...style,\n    '--tree-padding': `${level * indent}px`,\n  } as React.CSSProperties;\n  const contextItem = item as ItemInstance<unknown>;\n\n  const defaultProps = {\n    'data-slot': 'tree-item',\n    style: itemStyle,\n    'data-focus': stateOf(item.isFocused),\n    'data-folder': stateOf(item.isFolder),\n    'data-selected': stateOf(item.isSelected),\n    'data-drag-target': stateOf(item.isDragTarget),\n    'data-search-match': stateOf(item.isMatchingSearch),\n    'aria-expanded': item.isExpanded(),\n    className: cn(\n      'z-10 ps-(--tree-padding) outline-none select-none not-last:pb-0.5 focus:z-20 data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n      className\n    ),\n    children,\n  };\n\n  const element = useRender({\n    defaultTagName: 'button',\n    render,\n    props: mergeProps<'button'>(defaultProps, props, itemProps),\n  });\n\n  return (\n    <TreeContext.Provider value={{ indent, currentItem: contextItem }}>\n      {element}\n    </TreeContext.Provider>\n  );\n}\n\ntype TreeItemLabelProps<T> = React.ComponentProps<'span'> & {\n  item?: ItemInstance<T>;\n};\n\nconst LABEL = [\n  'flex items-center gap-1.5 rounded-md px-2 py-1.5 text-[13px] transition-colors not-in-data-[folder=true]:ps-7.5',\n  'hover:bg-foreground/5 in-data-[selected=true]:bg-primary/[0.08] in-data-[selected=true]:text-foreground',\n  'in-data-[drag-target=true]:bg-primary/10 in-data-[search-match=true]:bg-warning/15',\n  'in-focus-visible:ring-[3px] in-focus-visible:ring-ring/25',\n  \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n].join(' ');\n\nfunction TreeItemLabel<T>({\n  item: itemProp,\n  children,\n  className,\n  ...props\n}: TreeItemLabelProps<T>) {\n  const { currentItem } = useTreeContext();\n  const item = itemProp ?? currentItem;\n  if (!item) {\n    return null;\n  }\n  const isFolder = item.isFolder();\n  const label = children ?? item.getItemName();\n\n  return (\n    <span\n      data-slot='tree-item-label'\n      className={cn(LABEL, className)}\n      {...props}\n    >\n      {isFolder && (\n        <ChevronDownIcon className='text-muted-foreground size-4 transition-transform duration-200 ease-(--ease-out-strong) in-aria-[expanded=false]:-rotate-90' />\n      )}\n      {label}\n    </span>\n  );\n}\n\nfunction TreeDragLine({ className, ...props }: React.ComponentProps<'div'>) {\n  const { tree } = useTreeContext();\n  const hasDragLine = typeof tree?.getDragLineStyle === 'function';\n  if (!tree || !hasDragLine) {\n    return null;\n  }\n\n  return (\n    <div\n      style={tree.getDragLineStyle()}\n      className={cn(\n        'bg-primary before:border-primary before:bg-background absolute z-30 -mt-px h-0.5 w-[unset] before:absolute before:-top-[3px] before:left-0 before:size-2 before:rounded-full before:border-2',\n        className\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Tree, TreeDragLine, TreeItem, TreeItemLabel, useTree };\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "primitives"
  ],
  "type": "registry:ui"
}