val 0.4.0

An arbitrary precision calculator language
Documentation
import type { AstNode as AstNodeType, Range } from '@/lib/types';
import { ChevronDown, ChevronRight } from 'lucide-react';
import React, { memo, useCallback } from 'react';

interface AstNodeProps {
  expandedNodes: Set<AstNodeType>;
  level: number;
  node: AstNodeType;
  onHighlightChange: (range: Range | undefined) => void;
  toggleExpand: (node: AstNodeType) => void;
}

export const AstNode: React.FC<AstNodeProps> = memo(
  ({ expandedNodes, level, node, onHighlightChange, toggleExpand }) => {
    const hasChildren = node.children && node.children.length > 0;
    const isExpanded = expandedNodes.has(node);
    const isValidRange = node.range.start < node.range.end;

    const handleMouseEnter = useCallback(() => {
      if (isValidRange) {
        onHighlightChange(node.range);
      }
    }, [isValidRange, node.range, onHighlightChange]);

    const handleMouseLeave = useCallback(() => {
      onHighlightChange(undefined);
    }, [onHighlightChange]);

    const toggleExpanded = useCallback(() => {
      if (hasChildren) {
        toggleExpand(node);
      }
    }, [hasChildren, node, toggleExpand]);

    return (
      <>
        <div
          className='flex cursor-pointer items-center py-1 font-mono text-sm whitespace-nowrap transition-colors hover:bg-blue-50'
          onClick={toggleExpanded}
          onMouseLeave={handleMouseLeave}
          onMouseEnter={handleMouseEnter}
          style={{ paddingLeft: `${level * 16 + 4}px` }}
        >
          <span className='mr-1 flex w-4 justify-center'>
            {hasChildren ? (
              isExpanded ? (
                <ChevronDown size={14} />
              ) : (
                <ChevronRight size={14} />
              )
            ) : (
              <span className='w-4'></span>
            )}
          </span>

          <span>{node.kind}</span>

          <span className='ml-2 text-xs text-gray-500'>
            [{node.range.start}: {node.range.end}]{!isValidRange && ' (empty)'}
          </span>
        </div>

        {isExpanded &&
          hasChildren &&
          node.children.map((child, index) => (
            <AstNode
              key={`${child.kind}-${index}`}
              node={child}
              level={level + 1}
              expandedNodes={expandedNodes}
              toggleExpand={toggleExpand}
              onHighlightChange={onHighlightChange}
            />
          ))}
      </>
    );
  }
);

AstNode.displayName = 'AstNode';