'use client'; import { cn } from '@/lib/utils'; import * as AccordionPrimitive from '@radix-ui/react-accordion'; import { cva } from 'class-variance-authority'; import { ChevronRight } from 'lucide-react'; import React from 'react'; const treeVariants = cva( 'group hover:before:opacity-100 before:absolute before:rounded-lg before:left-0 px-2 before:w-full before:opacity-0 before:bg-accent/70 before:h-[2rem] before:-z-10', ); const selectedTreeVariants = cva( 'before:opacity-100 before:bg-[#4E74Fd]/70 text-accent-foreground', ); export interface TreeDataItem { id: string; name: string; icon?: any; selectedIcon?: any; openIcon?: any; children?: TreeDataItem[]; actions?: React.ReactNode; onClick?: () => void; } type TreeProps = React.HTMLAttributes & { data: TreeDataItem[] | TreeDataItem; initialSelectedItemId?: string; onSelectChange?: (item: TreeDataItem | undefined) => void; expandAll?: boolean; defaultNodeIcon?: any; defaultLeafIcon?: any; }; const TreeView = React.forwardRef( ( { data, initialSelectedItemId, onSelectChange, expandAll, defaultLeafIcon, defaultNodeIcon, className, ...props }, ref, ) => { const [selectedItemId, setSelectedItemId] = React.useState< string | undefined >(initialSelectedItemId); const handleSelectChange = React.useCallback( (item: TreeDataItem | undefined) => { setSelectedItemId(item?.id); if (onSelectChange) { onSelectChange(item); } }, [onSelectChange], ); const expandedItemIds = React.useMemo(() => { if (!initialSelectedItemId) { return [] as string[]; } const ids: string[] = []; function walkTreeItems( items: TreeDataItem[] | TreeDataItem, targetId: string, ) { if (items instanceof Array) { for (let i = 0; i < items.length; i++) { ids.push(items[i]!.id); if (walkTreeItems(items[i]!, targetId) && !expandAll) { return true; } if (!expandAll) ids.pop(); } } else if (!expandAll && items.id === targetId) { return true; } else if (items.children) { return walkTreeItems(items.children, targetId); } } walkTreeItems(data, initialSelectedItemId); return ids; }, [data, expandAll, initialSelectedItemId]); return (
); }, ); TreeView.displayName = 'TreeView'; type TreeItemProps = TreeProps & { selectedItemId?: string; handleSelectChange: (item: TreeDataItem | undefined) => void; expandedItemIds: string[]; defaultNodeIcon?: any; defaultLeafIcon?: any; }; const TreeItem = React.forwardRef( ( { className, data, selectedItemId, handleSelectChange, expandedItemIds, defaultNodeIcon, defaultLeafIcon, ...props }, ref, ) => { if (!(data instanceof Array)) { data = [data]; } return (
    {data.map((item) => (
  • {item.children ? ( ) : ( )}
  • ))}
); }, ); TreeItem.displayName = 'TreeItem'; const TreeNode = ({ item, handleSelectChange, expandedItemIds, selectedItemId, defaultNodeIcon, defaultLeafIcon, }: { item: TreeDataItem; handleSelectChange: (item: TreeDataItem | undefined) => void; expandedItemIds: string[]; selectedItemId?: string; defaultNodeIcon?: any; defaultLeafIcon?: any; }) => { const [value, setValue] = React.useState( expandedItemIds.includes(item.id) ? [item.id] : [], ); return ( setValue(s)} > { handleSelectChange(item); item.onClick?.(); }} > {item.name} {item.actions} ); }; const TreeLeaf = React.forwardRef< HTMLDivElement, React.HTMLAttributes & { item: TreeDataItem; selectedItemId?: string; handleSelectChange: (item: TreeDataItem | undefined) => void; defaultLeafIcon?: any; } >( ( { className, item, selectedItemId, handleSelectChange, defaultLeafIcon, ...props }, ref, ) => { return (
{ handleSelectChange(item); item.onClick?.(); }} {...props} > {item.name} {item.actions}
); }, ); TreeLeaf.displayName = 'TreeLeaf'; const AccordionTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( svg]:rotate-90', className, )} {...props} > {children} )); AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; const AccordionContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => (
{children}
)); AccordionContent.displayName = AccordionPrimitive.Content.displayName; const TreeIcon = ({ item, isOpen, isSelected, default: defaultIcon, }: { item: TreeDataItem; isOpen?: boolean; isSelected?: boolean; default?: any; }) => { let Icon = defaultIcon; if (isSelected && item.selectedIcon) { Icon = item.selectedIcon; } else if (isOpen && item.openIcon) { Icon = item.openIcon; } else if (item.icon) { Icon = item.icon; } return Icon ? : <>; }; const TreeActions = ({ children, isSelected, }: { children: React.ReactNode; isSelected: boolean; }) => { return (
{children}
); }; export { TreeView, type TreeDataItem };