1
0
Fork 0
polaris-task-force/src/components/admin/narrative-flow/NodeShell.tsx

95 lines
2.6 KiB
TypeScript

"use client";
import { Handle, Position } from "@xyflow/react";
import { NODE_SOCKETS, SOCKET_COLORS, type SocketDef } from "./types";
function SocketHandle({
socket,
isInput,
topPercent,
}: {
socket: SocketDef;
isInput: boolean;
topPercent: number;
}) {
const color = SOCKET_COLORS[socket.type];
const isFlow = socket.type === "flow";
return (
<div
className="absolute flex items-center gap-1"
style={{
[isInput ? "left" : "right"]: -8,
top: `${topPercent}%`,
transform: "translateY(-50%)",
flexDirection: isInput ? "row" : "row-reverse",
}}
>
<Handle
type={isInput ? "target" : "source"}
position={isInput ? Position.Left : Position.Right}
id={socket.id}
style={{
background: color,
width: isFlow ? 8 : 6,
height: isFlow ? 8 : 6,
borderRadius: isFlow ? 2 : "50%",
border: `1.5px solid ${color}80`,
position: "relative",
left: "auto",
right: "auto",
top: "auto",
flexShrink: 0,
}}
/>
{socket.label && (
<span
className="text-[9px] font-mono font-medium leading-none whitespace-nowrap pointer-events-none"
style={{ color, opacity: 0.9 }}
>
{socket.label}
</span>
)}
</div>
);
}
interface NodeShellProps {
nodeType: string;
dynamicInputs?: SocketDef[];
dynamicOutputs?: SocketDef[];
children: React.ReactNode;
className?: string;
}
export function NodeShell({
nodeType,
dynamicInputs,
dynamicOutputs,
children,
className = "",
}: NodeShellProps) {
const config = NODE_SOCKETS[nodeType];
if (!config) return <div className={className}>{children}</div>;
const inputs = dynamicInputs ?? config.inputs;
const outputs = dynamicOutputs ?? config.outputs;
const maxSlots = Math.max(inputs.length, outputs.length, 1);
return (
<div className={`relative ${className}`}>
{/* Input handles - positioned on left edge */}
{inputs.map((socket, i) => {
const top = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
return <SocketHandle key={socket.id} socket={socket} isInput={true} topPercent={top} />;
})}
{/* Output handles - positioned on right edge */}
{outputs.map((socket, i) => {
const top = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
return <SocketHandle key={socket.id} socket={socket} isInput={false} topPercent={top} />;
})}
{/* Content area with padding to avoid handle overlap */}
<div className="px-8">{children}</div>
</div>
);
}