import React, { useState } from 'react'; import { useStyles, useTheme } from '../../core'; import { Icon } from '../Icon/Icon'; import { ChevronRightIcon, LockIcon } from '../../icons'; import { LockOpenIcon } from '../../icons/LockOpenIcon'; export interface XNodeTreeData { id: string; label: string; icon?: React.ElementType; children?: XNodeTreeData[]; } interface XNodeTreeProps { data: XNodeTreeData[]; initialExpandedIds?: string[]; initialLockedIds?: string[]; } export const XNodeTree: React.FC = ({ data, initialExpandedIds = [], initialLockedIds = [], }) => { const { theme } = useTheme(); const createStyle = useStyles('x-node-tree'); const [expandedIds, setExpandedIds] = useState(new Set(initialExpandedIds)); const [lockedIds, setLockedIds] = useState(new Set(initialLockedIds)); const toggleExpand = (id: string) => { setExpandedIds(prev => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); }; const toggleLock = (id: string) => { setLockedIds(prev => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); }; const treeClass = createStyle({ color: theme.colors.text, fontSize: theme.typography.fontSizes.sm, userSelect: 'none', }); const nodeItemClass = createStyle({ display: 'flex', alignItems: 'center', padding: '4px 8px', borderRadius: '4px', cursor: 'pointer', transition: 'background-color 0.2s', '&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.05)', }, }); const iconClass = (isExpanded: boolean) => createStyle({ transition: 'transform 0.2s', transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)', }); const lockIconClass = createStyle({ marginLeft: 'auto', color: theme.colors.textSecondary, opacity: 0.5, '&:hover': { color: theme.colors.text, opacity: 1, }, }); const renderNode = (node: XNodeTreeData, level: number) => { const isExpanded = expandedIds.has(node.id); const isLocked = lockedIds.has(node.id); const hasChildren = node.children && node.children.length > 0; return (
hasChildren && toggleExpand(node.id)} > {node.icon && } {node.label} { e.stopPropagation(); toggleLock(node.id); }} />
{hasChildren && isExpanded && (
{node.children!.map(child => renderNode(child, level + 1))}
)}
); }; return
{data.map(node => renderNode(node, 0))}
; };