{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "comp-572",
  "type": "registry:component",
  "title": "Comp 572",
  "description": "Comp 572",
  "files": [
    {
      "path": "registry/ui-basic/comp-572.tsx",
      "content": "\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\n\nimport { Input } from \"@/components/ui/input\";\nimport { Tree, TreeItem, TreeItemLabel } from \"@/components/ui/tree\";\nimport {\n\texpandAllFeature,\n\thotkeysCoreFeature,\n\tsearchFeature,\n\tselectionFeature,\n\tsyncDataLoaderFeature,\n\tTreeState,\n} from \"@headless-tree/core\";\nimport { useTree } from \"@headless-tree/react\";\nimport {\n\tCircleXIcon,\n\tFilterIcon,\n\tFolderIcon,\n\tFolderOpenIcon,\n} from \"lucide-react\";\n\ninterface Item {\n\tname: string;\n\tchildren?: string[];\n}\n\nconst items: Record<string, Item> = {\n\tcompany: {\n\t\tname: \"Company\",\n\t\tchildren: [\"engineering\", \"marketing\", \"operations\"],\n\t},\n\tengineering: {\n\t\tname: \"Engineering\",\n\t\tchildren: [\"frontend\", \"backend\", \"platform-team\"],\n\t},\n\tfrontend: { name: \"Frontend\", children: [\"design-system\", \"web-platform\"] },\n\t\"design-system\": {\n\t\tname: \"Design System\",\n\t\tchildren: [\"components\", \"tokens\", \"guidelines\"],\n\t},\n\tcomponents: { name: \"Components\" },\n\ttokens: { name: \"Tokens\" },\n\tguidelines: { name: \"Guidelines\" },\n\t\"web-platform\": { name: \"Web Platform\" },\n\tbackend: { name: \"Backend\", children: [\"apis\", \"infrastructure\"] },\n\tapis: { name: \"APIs\" },\n\tinfrastructure: { name: \"Infrastructure\" },\n\t\"platform-team\": { name: \"Platform Team\" },\n\tmarketing: { name: \"Marketing\", children: [\"content\", \"seo\"] },\n\tcontent: { name: \"Content\" },\n\tseo: { name: \"SEO\" },\n\toperations: { name: \"Operations\", children: [\"hr\", \"finance\"] },\n\thr: { name: \"HR\" },\n\tfinance: { name: \"Finance\" },\n};\n\nconst indent = 20;\n\nexport default function Component() {\n\t// Store the initial expanded items to reset when search is cleared\n\tconst initialExpandedItems = [\"engineering\", \"frontend\", \"design-system\"];\n\tconst [state, setState] = useState<Partial<TreeState<Item>>>({});\n\tconst [searchValue, setSearchValue] = useState(\"\");\n\tconst inputRef = useRef<HTMLInputElement>(null);\n\n\tconst tree = useTree<Item>({\n\t\tstate,\n\t\tsetState,\n\t\tinitialState: {\n\t\t\texpandedItems: initialExpandedItems,\n\t\t},\n\t\tindent,\n\t\trootItemId: \"company\",\n\t\tgetItemName: (item) => item.getItemData().name,\n\t\tisItemFolder: (item) => (item.getItemData()?.children?.length ?? 0) > 0,\n\t\tdataLoader: {\n\t\t\tgetItem: (itemId) => items[itemId],\n\t\t\tgetChildren: (itemId) => items[itemId].children ?? [],\n\t\t},\n\t\tfeatures: [\n\t\t\tsyncDataLoaderFeature,\n\t\t\thotkeysCoreFeature,\n\t\t\tselectionFeature,\n\t\t\tsearchFeature,\n\t\t\texpandAllFeature,\n\t\t],\n\t});\n\n\t// Handle clearing the search\n\tconst handleClearSearch = () => {\n\t\tsetSearchValue(\"\");\n\n\t\t// Manually trigger the tree's search onChange with an empty value\n\t\t// to ensure item.isMatchingSearch() is correctly updated.\n\t\tconst searchProps = tree.getSearchInputElementProps();\n\t\tif (searchProps.onChange) {\n\t\t\tconst syntheticEvent = {\n\t\t\t\ttarget: { value: \"\" },\n\t\t\t} as React.ChangeEvent<HTMLInputElement>; // Cast to the expected event type\n\t\t\tsearchProps.onChange(syntheticEvent);\n\t\t}\n\n\t\t// Reset tree state to initial expanded items\n\t\tsetState((prevState) => ({\n\t\t\t...prevState,\n\t\t\texpandedItems: initialExpandedItems,\n\t\t}));\n\n\t\t// Clear custom filtered items\n\t\tsetFilteredItems([]);\n\n\t\tif (inputRef.current) {\n\t\t\tinputRef.current.focus();\n\t\t\t// Also clear the internal search input\n\t\t\tinputRef.current.value = \"\";\n\t\t}\n\t};\n\n\t// Keep track of filtered items separately from the tree's internal search state\n\tconst [filteredItems, setFilteredItems] = useState<string[]>([]);\n\n\t// This function determines if an item should be visible based on our custom filtering\n\tconst shouldShowItem = (itemId: string) => {\n\t\tif (!searchValue || searchValue.length === 0) return true;\n\t\treturn filteredItems.includes(itemId);\n\t};\n\n\t// Update filtered items when search value changes\n\tuseEffect(() => {\n\t\tif (!searchValue || searchValue.length === 0) {\n\t\t\tsetFilteredItems([]);\n\t\t\treturn;\n\t\t}\n\n\t\t// Get all items\n\t\tconst allItems = tree.getItems();\n\n\t\t// First, find direct matches\n\t\tconst directMatches = allItems\n\t\t\t.filter((item) => {\n\t\t\t\tconst name = item.getItemName().toLowerCase();\n\t\t\t\treturn name.includes(searchValue.toLowerCase());\n\t\t\t})\n\t\t\t.map((item) => item.getId());\n\n\t\t// Then, find all parent IDs of matching items\n\t\tconst parentIds = new Set<string>();\n\t\tdirectMatches.forEach((matchId) => {\n\t\t\tlet item = tree.getItems().find((i) => i.getId() === matchId);\n\t\t\twhile (item?.getParent && item.getParent()) {\n\t\t\t\tconst parent = item.getParent();\n\t\t\t\tif (parent) {\n\t\t\t\t\tparentIds.add(parent.getId());\n\t\t\t\t\titem = parent;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// Find all children of matching items\n\t\tconst childrenIds = new Set<string>();\n\t\tdirectMatches.forEach((matchId) => {\n\t\t\tconst item = tree.getItems().find((i) => i.getId() === matchId);\n\t\t\tif (item && item.isFolder()) {\n\t\t\t\t// Get all descendants recursively\n\t\t\t\tconst getDescendants = (itemId: string) => {\n\t\t\t\t\tconst children = items[itemId]?.children || [];\n\t\t\t\t\tchildren.forEach((childId) => {\n\t\t\t\t\t\tchildrenIds.add(childId);\n\t\t\t\t\t\tif (items[childId]?.children?.length) {\n\t\t\t\t\t\t\tgetDescendants(childId);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tgetDescendants(item.getId());\n\t\t\t}\n\t\t});\n\n\t\t// Combine direct matches, parents, and children\n\t\tsetFilteredItems([\n\t\t\t...directMatches,\n\t\t\t...Array.from(parentIds),\n\t\t\t...Array.from(childrenIds),\n\t\t]);\n\n\t\t// Keep all folders expanded during search to ensure all matches are visible\n\t\t// Store current expanded state first\n\t\tconst currentExpandedItems = tree.getState().expandedItems || [];\n\n\t\t// Get all folder IDs that need to be expanded to show matches\n\t\tconst folderIdsToExpand = allItems\n\t\t\t.filter((item) => item.isFolder())\n\t\t\t.map((item) => item.getId());\n\n\t\t// Update expanded items in the tree state\n\t\tsetState((prevState) => ({\n\t\t\t...prevState,\n\t\t\texpandedItems: [\n\t\t\t\t...new Set([...currentExpandedItems, ...folderIdsToExpand]),\n\t\t\t],\n\t\t}));\n\t}, [searchValue, tree]);\n\n\treturn (\n\t\t<div className=\"flex h-full flex-col gap-2 nth-2:*:grow\">\n\t\t\t<div className=\"relative\">\n\t\t\t\t<Input\n\t\t\t\t\tref={inputRef}\n\t\t\t\t\tclassName=\"peer ps-9\"\n\t\t\t\t\tvalue={searchValue}\n\t\t\t\t\tonChange={(e) => {\n\t\t\t\t\t\tconst value = e.target.value;\n\t\t\t\t\t\tsetSearchValue(value);\n\n\t\t\t\t\t\t// Apply the search to the tree's internal state as well\n\t\t\t\t\t\tconst searchProps = tree.getSearchInputElementProps();\n\t\t\t\t\t\tif (searchProps.onChange) {\n\t\t\t\t\t\t\tsearchProps.onChange(e);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (value.length > 0) {\n\t\t\t\t\t\t\t// If input has at least one character, expand all items\n\t\t\t\t\t\t\ttree.expandAll();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// If input is cleared, reset to initial expanded state\n\t\t\t\t\t\t\tsetState((prevState) => ({\n\t\t\t\t\t\t\t\t...prevState,\n\t\t\t\t\t\t\t\texpandedItems: initialExpandedItems,\n\t\t\t\t\t\t\t}));\n\t\t\t\t\t\t\tsetFilteredItems([]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\t// Prevent the internal search from being cleared on blur\n\t\t\t\t\tonBlur={(e) => {\n\t\t\t\t\t\t// Prevent default blur behavior\n\t\t\t\t\t\te.preventDefault();\n\n\t\t\t\t\t\t// Re-apply the search to ensure it stays active\n\t\t\t\t\t\tif (searchValue && searchValue.length > 0) {\n\t\t\t\t\t\t\tconst searchProps = tree.getSearchInputElementProps();\n\t\t\t\t\t\t\tif (searchProps.onChange) {\n\t\t\t\t\t\t\t\tconst syntheticEvent = {\n\t\t\t\t\t\t\t\t\ttarget: { value: searchValue },\n\t\t\t\t\t\t\t\t} as React.ChangeEvent<HTMLInputElement>;\n\t\t\t\t\t\t\t\tsearchProps.onChange(syntheticEvent);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}}\n\t\t\t\t\ttype=\"search\"\n\t\t\t\t\tplaceholder=\"Filter items...\"\n\t\t\t\t/>\n\t\t\t\t<div className=\"text-muted-foreground/80 pointer-events-none absolute inset-y-0 inset-s-0 flex items-center justify-center ps-3 peer-disabled:opacity-50\">\n\t\t\t\t\t<FilterIcon className=\"size-4\" aria-hidden=\"true\" />\n\t\t\t\t</div>\n\t\t\t\t{searchValue && (\n\t\t\t\t\t<button\n\t\t\t\t\t\tclassName=\"text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute inset-y-0 inset-e-0 flex h-full w-9 items-center justify-center rounded-e-md transition-[color,box-shadow] outline-hidden focus:z-10 focus-visible:ring-[3px] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\"\n\t\t\t\t\t\taria-label=\"Clear search\"\n\t\t\t\t\t\tonClick={handleClearSearch}\n\t\t\t\t\t>\n\t\t\t\t\t\t<CircleXIcon className=\"size-4\" aria-hidden=\"true\" />\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t<Tree indent={indent} tree={tree}>\n\t\t\t\t{searchValue && filteredItems.length === 0 ? (\n\t\t\t\t\t<p className=\"px-3 py-4 text-center text-sm\">\n\t\t\t\t\t\tNo items found for \"{searchValue}\"\n\t\t\t\t\t</p>\n\t\t\t\t) : (\n\t\t\t\t\ttree.getItems().map((item) => {\n\t\t\t\t\t\tconst isVisible = shouldShowItem(item.getId());\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<TreeItem\n\t\t\t\t\t\t\t\tkey={item.getId()}\n\t\t\t\t\t\t\t\titem={item}\n\t\t\t\t\t\t\t\tdata-visible={isVisible || !searchValue}\n\t\t\t\t\t\t\t\tclassName=\"data-[visible=false]:hidden\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<TreeItemLabel>\n\t\t\t\t\t\t\t\t\t<span className=\"flex items-center gap-2\">\n\t\t\t\t\t\t\t\t\t\t{item.isFolder() &&\n\t\t\t\t\t\t\t\t\t\t\t(item.isExpanded() ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<FolderOpenIcon className=\"text-muted-foreground pointer-events-none size-4\" />\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<FolderIcon className=\"text-muted-foreground pointer-events-none size-4\" />\n\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t{item.getItemName()}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</TreeItemLabel>\n\t\t\t\t\t\t\t</TreeItem>\n\t\t\t\t\t\t);\n\t\t\t\t\t})\n\t\t\t\t)}\n\t\t\t</Tree>\n\n\t\t\t<p\n\t\t\t\taria-live=\"polite\"\n\t\t\t\trole=\"region\"\n\t\t\t\tclassName=\"text-muted-foreground mt-2 text-xs\"\n\t\t\t>\n\t\t\t\tTree with filtering ∙{\" \"}\n\t\t\t\t<a\n\t\t\t\t\thref=\"https://headless-tree.lukasbach.com\"\n\t\t\t\t\tclassName=\"hover:text-foreground underline\"\n\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t>\n\t\t\t\t\tAPI\n\t\t\t\t</a>\n\t\t\t</p>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/input.tsx",
      "content": "// SHADCN UI GENERATED CODE\n\nimport React from \"react\";\n\nimport { cn } from \"@/registry/utilities/cn\";\n\nexport interface InputProps\n\textends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n\t({ className, type, ...props }, ref) => {\n\t\treturn (\n\t\t\t<input\n\t\t\t\ttype={type}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t\tclassName\n\t\t\t\t)}\n\t\t\t\tref={ref}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t}\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/tree.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/registry/utilities/cn\";\nimport { ItemInstance } from \"@headless-tree/core\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport { Slot } from \"radix-ui\";\n\ninterface TreeContextValue<T = any> {\n\tindent: number;\n\tcurrentItem?: ItemInstance<T>;\n\ttree?: any;\n}\n\nconst TreeContext = React.createContext<TreeContextValue>({\n\tindent: 20,\n\tcurrentItem: undefined,\n\ttree: undefined,\n});\n\nfunction useTreeContext<T = any>() {\n\treturn React.useContext(TreeContext) as TreeContextValue<T>;\n}\n\ninterface TreeProps extends React.HTMLAttributes<HTMLDivElement> {\n\tindent?: number;\n\ttree?: any;\n}\n\nfunction Tree({ indent = 20, tree, className, ...props }: TreeProps) {\n\tconst containerProps =\n\t\ttree && typeof tree.getContainerProps === \"function\"\n\t\t\t? tree.getContainerProps()\n\t\t\t: {};\n\tconst mergedProps = { ...props, ...containerProps };\n\n\t// Extract style from mergedProps to merge with our custom styles\n\tconst { style: propStyle, ...otherProps } = mergedProps;\n\n\t// Merge styles\n\tconst mergedStyle = {\n\t\t...propStyle,\n\t\t\"--tree-indent\": `${indent}px`,\n\t} as React.CSSProperties;\n\n\treturn (\n\t\t<TreeContext.Provider value={{ indent, tree }}>\n\t\t\t<div\n\t\t\t\tdata-slot=\"tree\"\n\t\t\t\tstyle={mergedStyle}\n\t\t\t\tclassName={cn(\"flex flex-col\", className)}\n\t\t\t\t{...otherProps}\n\t\t\t/>\n\t\t</TreeContext.Provider>\n\t);\n}\n\ninterface TreeItemProps<T = any>\n\textends React.HTMLAttributes<HTMLButtonElement> {\n\titem: ItemInstance<T>;\n\tindent?: number;\n\tasChild?: boolean;\n}\n\nfunction TreeItem<T = any>({\n\titem,\n\tclassName,\n\tasChild,\n\tchildren,\n\t...props\n}: Omit<TreeItemProps<T>, \"indent\">) {\n\tconst { indent } = useTreeContext<T>();\n\n\tconst itemProps = typeof item.getProps === \"function\" ? item.getProps() : {};\n\tconst mergedProps = { ...props, ...itemProps };\n\n\t// Extract style from mergedProps to merge with our custom styles\n\tconst { style: propStyle, ...otherProps } = mergedProps;\n\n\t// Merge styles\n\tconst mergedStyle = {\n\t\t...propStyle,\n\t\t\"--tree-padding\": `${item.getItemMeta().level * indent}px`,\n\t} as React.CSSProperties;\n\n\tconst Comp = asChild ? Slot.Root : \"button\";\n\n\treturn (\n\t\t<TreeContext.Provider value={{ indent, currentItem: item }}>\n\t\t\t<Comp\n\t\t\t\tdata-slot=\"tree-item\"\n\t\t\t\tstyle={mergedStyle}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"z-10 ps-(--tree-padding) outline-hidden select-none not-last:pb-0.5 focus:z-20 data-disabled:pointer-events-none data-disabled:opacity-50\",\n\t\t\t\t\tclassName\n\t\t\t\t)}\n\t\t\t\tdata-focus={\n\t\t\t\t\ttypeof item.isFocused === \"function\"\n\t\t\t\t\t\t? item.isFocused() || false\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tdata-folder={\n\t\t\t\t\ttypeof item.isFolder === \"function\"\n\t\t\t\t\t\t? item.isFolder() || false\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tdata-selected={\n\t\t\t\t\ttypeof item.isSelected === \"function\"\n\t\t\t\t\t\t? item.isSelected() || false\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tdata-drag-target={\n\t\t\t\t\ttypeof item.isDragTarget === \"function\"\n\t\t\t\t\t\t? item.isDragTarget() || false\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tdata-search-match={\n\t\t\t\t\ttypeof item.isMatchingSearch === \"function\"\n\t\t\t\t\t\t? item.isMatchingSearch() || false\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\taria-expanded={item.isExpanded()}\n\t\t\t\t{...otherProps}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</Comp>\n\t\t</TreeContext.Provider>\n\t);\n}\n\ninterface TreeItemLabelProps<T = any>\n\textends React.HTMLAttributes<HTMLSpanElement> {\n\titem?: ItemInstance<T>;\n}\n\nfunction TreeItemLabel<T = any>({\n\titem: propItem,\n\tchildren,\n\tclassName,\n\t...props\n}: TreeItemLabelProps<T>) {\n\tconst { currentItem } = useTreeContext<T>();\n\tconst item = propItem || currentItem;\n\n\tif (!item) {\n\t\tconsole.warn(\"TreeItemLabel: No item provided via props or context\");\n\t\treturn null;\n\t}\n\n\treturn (\n\t\t<span\n\t\t\tdata-slot=\"tree-item-label\"\n\t\t\tclassName={cn(\n\t\t\t\t\"in-focus-visible:ring-ring/50 bg-background hover:bg-accent in-data-[selected=true]:bg-accent in-data-[selected=true]:text-accent-foreground in-data-[drag-target=true]:bg-accent flex items-center gap-1 rounded-sm px-2 py-1.5 text-sm transition-colors not-in-data-[folder=true]:ps-7 in-focus-visible:ring-[3px] in-data-[search-match=true]:bg-blue-400/20! [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n\t\t\t\tclassName\n\t\t\t)}\n\t\t\t{...props}\n\t\t>\n\t\t\t{item.isFolder() && (\n\t\t\t\t<ChevronDownIcon className=\"text-muted-foreground size-4 in-aria-[expanded=false]:-rotate-90\" />\n\t\t\t)}\n\t\t\t{children ||\n\t\t\t\t(typeof item.getItemName === \"function\"\n\t\t\t\t\t? item.getItemName()\n\t\t\t\t\t: null)}\n\t\t</span>\n\t);\n}\n\nfunction TreeDragLine({\n\tclassName,\n\t...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n\tconst { tree } = useTreeContext();\n\n\tif (!tree || typeof tree.getDragLineStyle !== \"function\") {\n\t\tconsole.warn(\n\t\t\t\"TreeDragLine: No tree provided via context or tree does not have getDragLineStyle method\"\n\t\t);\n\t\treturn null;\n\t}\n\n\tconst dragLine = tree.getDragLineStyle();\n\treturn (\n\t\t<div\n\t\t\tstyle={dragLine}\n\t\t\tclassName={cn(\n\t\t\t\t\"bg-primary before:bg-background before:border-primary 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\t\t\t\tclassName\n\t\t\t)}\n\t\t\t{...props}\n\t\t/>\n\t);\n}\n\nexport { Tree, TreeItem, TreeItemLabel, TreeDragLine };\n",
      "type": "registry:ui"
    }
  ]
}