Files
Atomizer/atomizer-dashboard/frontend/src/components/canvas/nodes/BaseNode.tsx
Anto01 7919511bb2 feat: Phase 1 - Canvas with React Flow
- 8 node types (Model, Solver, DesignVar, Extractor, Objective, Constraint, Algorithm, Surrogate)
- Drag-drop from palette to canvas
- Node configuration panels
- Graph validation with error/warning display
- Intent JSON serialization
- Zustand state management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 20:00:35 -05:00

73 lines
1.8 KiB
TypeScript

import { memo, ReactNode } from 'react';
import { Handle, Position, NodeProps } from 'reactflow';
import { BaseNodeData } from '../../../lib/canvas/schema';
interface BaseNodeProps extends NodeProps<BaseNodeData> {
icon: ReactNode;
color: string;
children?: ReactNode;
inputs?: number;
outputs?: number;
}
function BaseNodeComponent({
data,
selected,
icon,
color,
children,
inputs = 1,
outputs = 1,
}: BaseNodeProps) {
return (
<div
className={`
px-4 py-3 rounded-lg border-2 min-w-[180px] bg-white shadow-sm
transition-all duration-200
${selected ? 'border-blue-500 shadow-lg' : 'border-gray-200'}
${!data.configured ? 'border-dashed' : ''}
${data.errors?.length ? 'border-red-400' : ''}
`}
>
{/* Input handles */}
{inputs > 0 && (
<Handle
type="target"
position={Position.Left}
className="w-3 h-3 !bg-gray-400"
/>
)}
{/* Header */}
<div className="flex items-center gap-2 mb-2">
<span className={`text-lg ${color}`}>{icon}</span>
<span className="font-medium text-gray-800">{data.label}</span>
{!data.configured && (
<span className="text-xs text-orange-500">!</span>
)}
</div>
{/* Content */}
{children && <div className="text-sm text-gray-600">{children}</div>}
{/* Errors */}
{data.errors?.length ? (
<div className="mt-2 text-xs text-red-500">
{data.errors[0]}
</div>
) : null}
{/* Output handles */}
{outputs > 0 && (
<Handle
type="source"
position={Position.Right}
className="w-3 h-3 !bg-gray-400"
/>
)}
</div>
);
}
export const BaseNode = memo(BaseNodeComponent);