);
}
diff --git a/src/components/admin/narrative-flow/LogicNode.tsx b/src/components/admin/narrative-flow/LogicNode.tsx
index 556508f..f8f2d79 100644
--- a/src/components/admin/narrative-flow/LogicNode.tsx
+++ b/src/components/admin/narrative-flow/LogicNode.tsx
@@ -2,24 +2,19 @@
import { type NodeProps } from "@xyflow/react";
import { BinaryIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function LogicNode({ data }: NodeProps) {
const d = data as { operator?: string };
const op = d.operator ?? "AND";
return (
-
+
);
}
diff --git a/src/components/admin/narrative-flow/MathNode.tsx b/src/components/admin/narrative-flow/MathNode.tsx
index 2eb32da..658a204 100644
--- a/src/components/admin/narrative-flow/MathNode.tsx
+++ b/src/components/admin/narrative-flow/MathNode.tsx
@@ -2,24 +2,19 @@
import { type NodeProps } from "@xyflow/react";
import { CalculatorIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function MathNode({ data }: NodeProps) {
const d = data as { operator?: string };
const op = d.operator ?? "+";
return (
-
+
);
}
diff --git a/src/components/admin/narrative-flow/NarrativeBeatNode.tsx b/src/components/admin/narrative-flow/NarrativeBeatNode.tsx
index 50d6bfd..4102a8a 100644
--- a/src/components/admin/narrative-flow/NarrativeBeatNode.tsx
+++ b/src/components/admin/narrative-flow/NarrativeBeatNode.tsx
@@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { BookOpenTextIcon, PencilIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function NarrativeBeatNode({ data }: NodeProps) {
const d = data as { title?: string; text?: string };
return (
-
-
+
@@ -20,6 +19,6 @@ export function NarrativeBeatNode({ data }: NodeProps) {
{d.text || "No description."}
-
+
);
}
diff --git a/src/components/admin/narrative-flow/NodeHandles.tsx b/src/components/admin/narrative-flow/NodeHandles.tsx
deleted file mode 100644
index 5cf33c6..0000000
--- a/src/components/admin/narrative-flow/NodeHandles.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-"use client";
-
-import { TypedHandle } from "./TypedHandle";
-import { NODE_SOCKETS, type SocketDef } from "./types";
-
-interface NodeHandlesProps {
- nodeType: string;
- dynamicInputs?: SocketDef[];
- dynamicOutputs?: SocketDef[];
-}
-
-export function NodeHandles({ nodeType, dynamicInputs, dynamicOutputs }: NodeHandlesProps) {
- const config = NODE_SOCKETS[nodeType];
- if (!config) return null;
-
- const inputs = dynamicInputs ?? config.inputs;
- const outputs = dynamicOutputs ?? config.outputs;
-
- const maxSlots = Math.max(inputs.length, outputs.length, 1);
-
- return (
- <>
- {inputs.map((socket, i) => {
- const topPercent = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
- return (
-
- );
- })}
- {outputs.map((socket, i) => {
- const topPercent = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
- return (
-
- );
- })}
- >
- );
-}
diff --git a/src/components/admin/narrative-flow/NodeShell.tsx b/src/components/admin/narrative-flow/NodeShell.tsx
new file mode 100644
index 0000000..17fcc01
--- /dev/null
+++ b/src/components/admin/narrative-flow/NodeShell.tsx
@@ -0,0 +1,99 @@
+"use client";
+
+import { Handle, Position } from "@xyflow/react";
+import { SOCKET_COLORS, NODE_SOCKETS, type SocketDef, type SocketType } 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 (
+
+
+ {socket.label && (
+
+ {socket.label}
+
+ )}
+
+ );
+}
+
+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
{children}
;
+
+ const inputs = dynamicInputs ?? config.inputs;
+ const outputs = dynamicOutputs ?? config.outputs;
+ const maxSlots = Math.max(inputs.length, outputs.length, 1);
+
+ return (
+
+ {/* Input handles - positioned on left edge */}
+ {inputs.map((socket, i) => {
+ const top = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
+ return (
+
+ );
+ })}
+ {/* Output handles - positioned on right edge */}
+ {outputs.map((socket, i) => {
+ const top = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
+ return (
+
+ );
+ })}
+ {/* Content area with padding to avoid handle overlap */}
+
{children}
+
+ );
+}
diff --git a/src/components/admin/narrative-flow/NotNode.tsx b/src/components/admin/narrative-flow/NotNode.tsx
index b0dd839..db94c99 100644
--- a/src/components/admin/narrative-flow/NotNode.tsx
+++ b/src/components/admin/narrative-flow/NotNode.tsx
@@ -2,21 +2,16 @@
import { type NodeProps } from "@xyflow/react";
import { ToggleLeftIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function NotNode(_props: NodeProps) {
return (
-
-
+
-
- NOT
-
+ NOT
-
- Inverts boolean
-
-
+
Inverts boolean
+
);
}
diff --git a/src/components/admin/narrative-flow/NumberNode.tsx b/src/components/admin/narrative-flow/NumberNode.tsx
index 219bda2..12b7368 100644
--- a/src/components/admin/narrative-flow/NumberNode.tsx
+++ b/src/components/admin/narrative-flow/NumberNode.tsx
@@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { HashIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function NumberNode({ data }: NodeProps) {
const d = data as { value?: number; description?: string };
return (
-
-
+
@@ -19,6 +18,6 @@ export function NumberNode({ data }: NodeProps) {
{d.description || "Numeric value"}
-
+
);
}
diff --git a/src/components/admin/narrative-flow/OutcomeNode.tsx b/src/components/admin/narrative-flow/OutcomeNode.tsx
index 6a13baa..65f259a 100644
--- a/src/components/admin/narrative-flow/OutcomeNode.tsx
+++ b/src/components/admin/narrative-flow/OutcomeNode.tsx
@@ -2,7 +2,7 @@
import { type NodeProps } from "@xyflow/react";
import { FlagIcon, PencilIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function OutcomeNode({ data }: NodeProps) {
const d = data as {
@@ -13,8 +13,7 @@ export function OutcomeNode({ data }: NodeProps) {
const effects = d.effects ?? [];
return (
-
-
+
@@ -41,6 +40,6 @@ export function OutcomeNode({ data }: NodeProps) {
))
)}
-
+
);
}
diff --git a/src/components/admin/narrative-flow/RandomNode.tsx b/src/components/admin/narrative-flow/RandomNode.tsx
index 5e37c0b..3f70ec7 100644
--- a/src/components/admin/narrative-flow/RandomNode.tsx
+++ b/src/components/admin/narrative-flow/RandomNode.tsx
@@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { ShuffleIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function RandomNode({ data }: NodeProps) {
const d = data as { min?: number; max?: number; description?: string };
return (
-
-
+
@@ -19,6 +18,6 @@ export function RandomNode({ data }: NodeProps) {
{d.description || "Random number generator"}
-
+
);
}
diff --git a/src/components/admin/narrative-flow/SequenceNode.tsx b/src/components/admin/narrative-flow/SequenceNode.tsx
index ea0327a..2fb1273 100644
--- a/src/components/admin/narrative-flow/SequenceNode.tsx
+++ b/src/components/admin/narrative-flow/SequenceNode.tsx
@@ -2,23 +2,20 @@
import { type NodeProps } from "@xyflow/react";
import { ListOrderedIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function SequenceNode(_props: NodeProps) {
return (
-
-
+
-
- Sequence
-
+ Sequence
1
2
3
-
+
);
}
diff --git a/src/components/admin/narrative-flow/StringNode.tsx b/src/components/admin/narrative-flow/StringNode.tsx
index aa4d6d8..fd49a99 100644
--- a/src/components/admin/narrative-flow/StringNode.tsx
+++ b/src/components/admin/narrative-flow/StringNode.tsx
@@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { TypeIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function StringNode({ data }: NodeProps) {
const d = data as { value?: string; description?: string };
return (
-
-
+
@@ -19,6 +18,6 @@ export function StringNode({ data }: NodeProps) {
{d.description || "String value"}
-
+
);
}
diff --git a/src/components/admin/narrative-flow/TypedHandle.tsx b/src/components/admin/narrative-flow/TypedHandle.tsx
deleted file mode 100644
index 0593e23..0000000
--- a/src/components/admin/narrative-flow/TypedHandle.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-"use client";
-
-import { Handle, Position } from "@xyflow/react";
-import { SOCKET_COLORS, type SocketType } from "./types";
-
-interface TypedHandleProps {
- id: string;
- socketType: SocketType;
- isInput: boolean;
- label?: string;
- style?: React.CSSProperties;
-}
-
-export function TypedHandle({ id, socketType, isInput, label, style }: TypedHandleProps) {
- const color = SOCKET_COLORS[socketType];
- const isFlow = socketType === "flow";
-
- return (
-
-
- {label && (
-
- {label}
-
- )}
-
- );
-}
diff --git a/src/components/admin/narrative-flow/WaitNode.tsx b/src/components/admin/narrative-flow/WaitNode.tsx
index 869d48b..3bd7b92 100644
--- a/src/components/admin/narrative-flow/WaitNode.tsx
+++ b/src/components/admin/narrative-flow/WaitNode.tsx
@@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { TimerIcon } from "lucide-react";
-import { NodeHandles } from "./NodeHandles";
+import { NodeShell } from "./NodeShell";
export function WaitNode({ data }: NodeProps) {
const d = data as { ticks?: number; input?: unknown };
return (
-
-
+
@@ -21,6 +20,6 @@ export function WaitNode({ data }: NodeProps) {
? `Input: ${String(d.input).substring(0, 50)}${String(d.input).length > 50 ? "..." : ""}`
: "No input"}
-
+
);
}