1
0
Fork 0

refactor: LiteGraph-style node layout with NodeShell wrapper

- NodeShell component wraps all nodes with handle rendering + px-8 content padding
- Handles positioned at node edges with labels outside (no overlap with content)
- Labels: 9px mono font, full opacity, color-matched to socket type
- Removed old TypedHandle + NodeHandles (replaced by NodeShell internals)
- All 18 node components updated to use NodeShell
- Format node dynamically generates handles from template {0}, {1}, etc.
This commit is contained in:
Jason Fraley 2026-07-28 18:26:13 -04:00
parent 707562faaa
commit 2ffd5e2a84
21 changed files with 165 additions and 213 deletions

View file

@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { CheckCircleIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
export function BooleanNode({ data }: NodeProps) {
const d = data as { value?: boolean; description?: string };
return (
<div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-28">
<NodeHandles nodeType="boolean" />
<NodeShell nodeType="boolean" className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-28">
<div className="flex items-center gap-2 px-3 py-2 border-b border-emerald-500/30">
<CheckCircleIcon className="size-4 text-emerald-400 shrink-0" />
<span className="text-sm font-medium text-emerald-200 flex-1 truncate">
@ -19,6 +18,6 @@ export function BooleanNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-emerald-300/80 line-clamp-2">
{d.description || "Boolean flag"}
</div>
</div>
</NodeShell>
);
}

View file

@ -2,22 +2,19 @@
import { type NodeProps } from "@xyflow/react";
import { GitBranchIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
export function BranchNode(_props: NodeProps) {
return (
<div className="bg-rose-950/80 border border-rose-500/50 rounded-lg shadow-md min-w-36">
<NodeHandles nodeType="branch" />
<NodeShell nodeType="branch" className="bg-rose-950/80 border border-rose-500/50 rounded-lg shadow-md min-w-36">
<div className="flex items-center gap-2 px-3 py-2 border-b border-rose-500/30">
<GitBranchIcon className="size-4 text-rose-400 shrink-0" />
<span className="text-sm font-medium text-rose-200 flex-1">
Branch
</span>
<span className="text-sm font-medium text-rose-200 flex-1">Branch</span>
</div>
<div className="px-3 py-2 flex justify-between text-xs">
<span className="text-emerald-400">true</span>
<span className="text-red-400">false</span>
</div>
</div>
</NodeShell>
);
}

View file

@ -2,15 +2,14 @@
import { type NodeProps } from "@xyflow/react";
import { PencilIcon, WaypointsIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
export function ChoiceNode({ data }: NodeProps) {
const d = data as { question?: string; options?: Array<{ id: string; label: string }> };
const options = d.options ?? [];
return (
<div className="bg-amber-950/80 border border-amber-500/50 rounded-lg shadow-md min-w-56">
<NodeHandles nodeType="choice" />
<NodeShell nodeType="choice" className="bg-amber-950/80 border border-amber-500/50 rounded-lg shadow-md min-w-56">
<div className="flex items-center gap-2 px-3 py-2 border-b border-amber-500/30">
<WaypointsIcon className="size-4 text-amber-400 shrink-0" />
<span className="text-sm font-medium text-amber-200 flex-1 truncate">
@ -33,6 +32,6 @@ export function ChoiceNode({ data }: NodeProps) {
))
)}
</div>
</div>
</NodeShell>
);
}

View file

@ -2,24 +2,19 @@
import { type NodeProps } from "@xyflow/react";
import { EqualIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
export function CompareNode({ data }: NodeProps) {
const d = data as { operator?: string };
const op = d.operator ?? ">";
return (
<div className="bg-slate-950/80 border border-slate-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="compare" />
<NodeShell nodeType="compare" className="bg-slate-950/80 border border-slate-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-slate-500/30">
<EqualIcon className="size-4 text-slate-400 shrink-0" />
<span className="text-sm font-medium text-slate-200 flex-1">
Compare
</span>
<span className="text-sm font-medium text-slate-200 flex-1">Compare</span>
</div>
<div className="px-3 py-2 text-center text-lg font-mono text-slate-300">
{op}
</div>
</div>
<div className="px-3 py-2 text-center text-lg font-mono text-slate-300">{op}</div>
</NodeShell>
);
}

View file

@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { CalendarIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
export function DateNode({ data }: NodeProps) {
const d = data as { value?: string; description?: string };
return (
<div className="bg-cyan-950/80 border border-cyan-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="date" />
<NodeShell nodeType="date" className="bg-cyan-950/80 border border-cyan-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-cyan-500/30">
<CalendarIcon className="size-4 text-cyan-400 shrink-0" />
<span className="text-sm font-medium text-cyan-200 flex-1 truncate">
@ -19,6 +18,6 @@ export function DateNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-cyan-300/80 line-clamp-2">
{d.description || "Date value"}
</div>
</div>
</NodeShell>
);
}

View file

@ -2,14 +2,13 @@
import { type NodeProps } from "@xyflow/react";
import { FlagIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
export function FlagNode({ data }: NodeProps) {
const d = data as { flagName?: string };
return (
<div className="bg-orange-950/80 border border-orange-500/50 rounded-lg shadow-md min-w-40">
<NodeHandles nodeType="flag" />
<NodeShell nodeType="flag" className="bg-orange-950/80 border border-orange-500/50 rounded-lg shadow-md min-w-40">
<div className="flex items-center gap-2 px-3 py-2 border-b border-orange-500/30">
<FlagIcon className="size-4 text-orange-400 shrink-0" />
<span className="text-sm font-medium text-orange-200 flex-1 truncate">
@ -19,6 +18,6 @@ export function FlagNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-orange-300/80 truncate">
{d.flagName || "Unnamed flag"}
</div>
</div>
</NodeShell>
);
}

View file

@ -2,7 +2,7 @@
import { type NodeProps } from "@xyflow/react";
import { AlignLeftIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
import { NODE_SOCKETS, type SocketDef } from "./types";
function parseTemplateArgs(template: string): SocketDef[] {
@ -25,17 +25,12 @@ export function FormatNode({ data }: NodeProps) {
];
return (
<div className="bg-sky-950/80 border border-sky-500/50 rounded-lg shadow-md min-w-40">
<NodeHandles nodeType="format" dynamicInputs={dynamicInputs} />
<NodeShell nodeType="format" dynamicInputs={dynamicInputs} className="bg-sky-950/80 border border-sky-500/50 rounded-lg shadow-md min-w-40">
<div className="flex items-center gap-2 px-3 py-2 border-b border-sky-500/30">
<AlignLeftIcon className="size-4 text-sky-400 shrink-0" />
<span className="text-sm font-medium text-sky-200 flex-1">
Format
</span>
<span className="text-sm font-medium text-sky-200 flex-1">Format</span>
</div>
<div className="px-3 py-2 text-xs text-sky-300/80 font-mono truncate">
{template}
</div>
</div>
<div className="px-3 py-2 text-xs text-sky-300/80 font-mono truncate">{template}</div>
</NodeShell>
);
}

View file

@ -2,7 +2,7 @@
import { type NodeProps } from "@xyflow/react";
import { DatabaseIcon } from "lucide-react";
import { NodeHandles } from "./NodeHandles";
import { NodeShell } from "./NodeShell";
const GAME_DATA_FIELDS = [
{ value: "structure_health", label: "Structure Health" },
@ -20,8 +20,7 @@ export function GameDataNode({ data }: NodeProps) {
const fieldLabel = GAME_DATA_FIELDS.find((f) => f.value === d.field)?.label ?? "Select field";
return (
<div className="bg-indigo-950/80 border border-indigo-500/50 rounded-lg shadow-md min-w-44">
<NodeHandles nodeType="gameData" />
<NodeShell nodeType="gameData" className="bg-indigo-950/80 border border-indigo-500/50 rounded-lg shadow-md min-w-44">
<div className="flex items-center gap-2 px-3 py-2 border-b border-indigo-500/30">
<DatabaseIcon className="size-4 text-indigo-400 shrink-0" />
<span className="text-sm font-medium text-indigo-200 flex-1 truncate">
@ -31,6 +30,6 @@ export function GameDataNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-indigo-300/80">
{fieldLabel}
</div>
</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-lime-950/80 border border-lime-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="logic" />
<NodeShell nodeType="logic" className="bg-lime-950/80 border border-lime-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-lime-500/30">
<BinaryIcon className="size-4 text-lime-400 shrink-0" />
<span className="text-sm font-medium text-lime-200 flex-1">
Logic
</span>
<span className="text-sm font-medium text-lime-200 flex-1">Logic</span>
</div>
<div className="px-3 py-2 text-center text-sm font-mono text-lime-300">
{op}
</div>
</div>
<div className="px-3 py-2 text-center text-sm font-mono text-lime-300">{op}</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-violet-950/80 border border-violet-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="math" />
<NodeShell nodeType="math" className="bg-violet-950/80 border border-violet-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-violet-500/30">
<CalculatorIcon className="size-4 text-violet-400 shrink-0" />
<span className="text-sm font-medium text-violet-200 flex-1">
Math
</span>
<span className="text-sm font-medium text-violet-200 flex-1">Math</span>
</div>
<div className="px-3 py-2 text-center text-lg font-mono text-violet-300">
{op}
</div>
</div>
<div className="px-3 py-2 text-center text-lg font-mono text-violet-300">{op}</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-blue-950/80 border border-blue-500/50 rounded-lg shadow-md min-w-56">
<NodeHandles nodeType="narrativeBeat" />
<NodeShell nodeType="narrativeBeat" className="bg-blue-950/80 border border-blue-500/50 rounded-lg shadow-md min-w-56">
<div className="flex items-center gap-2 px-3 py-2 border-b border-blue-500/30">
<BookOpenTextIcon className="size-4 text-blue-400 shrink-0" />
<span className="text-sm font-medium text-blue-200 flex-1 truncate">
@ -20,6 +19,6 @@ export function NarrativeBeatNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-blue-300/80 line-clamp-3">
{d.text || "No description."}
</div>
</div>
</NodeShell>
);
}

View file

@ -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 (
<TypedHandle
key={socket.id}
id={socket.id}
socketType={socket.type}
label={socket.label}
isInput={true}
style={{ top: `${topPercent}%` }}
/>
);
})}
{outputs.map((socket, i) => {
const topPercent = maxSlots === 1 ? 50 : (i / (maxSlots - 1)) * 80 + 10;
return (
<TypedHandle
key={socket.id}
id={socket.id}
socketType={socket.type}
label={socket.label}
isInput={false}
style={{ top: `${topPercent}%` }}
/>
);
})}
</>
);
}

View file

@ -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 (
<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>
);
}

View file

@ -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 (
<div className="bg-red-950/80 border border-red-500/50 rounded-lg shadow-md min-w-28">
<NodeHandles nodeType="not" />
<NodeShell nodeType="not" className="bg-red-950/80 border border-red-500/50 rounded-lg shadow-md min-w-28">
<div className="flex items-center gap-2 px-3 py-2 border-b border-red-500/30">
<ToggleLeftIcon className="size-4 text-red-400 shrink-0" />
<span className="text-sm font-medium text-red-200 flex-1">
NOT
</span>
<span className="text-sm font-medium text-red-200 flex-1">NOT</span>
</div>
<div className="px-3 py-2 text-xs text-red-300/80">
Inverts boolean
</div>
</div>
<div className="px-3 py-2 text-xs text-red-300/80">Inverts boolean</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-purple-950/80 border border-purple-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="number" />
<NodeShell nodeType="number" className="bg-purple-950/80 border border-purple-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-purple-500/30">
<HashIcon className="size-4 text-purple-400 shrink-0" />
<span className="text-sm font-medium text-purple-200 flex-1 truncate">
@ -19,6 +18,6 @@ export function NumberNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-purple-300/80 line-clamp-2">
{d.description || "Numeric value"}
</div>
</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-56">
<NodeHandles nodeType="outcome" />
<NodeShell nodeType="outcome" className="bg-emerald-950/80 border border-emerald-500/50 rounded-lg shadow-md min-w-56">
<div className="flex items-center gap-2 px-3 py-2 border-b border-emerald-500/30">
<FlagIcon className="size-4 text-emerald-400 shrink-0" />
<span className="text-sm font-medium text-emerald-200 flex-1 truncate">
@ -41,6 +40,6 @@ export function OutcomeNode({ data }: NodeProps) {
))
)}
</div>
</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-pink-950/80 border border-pink-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="random" />
<NodeShell nodeType="random" className="bg-pink-950/80 border border-pink-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-pink-500/30">
<ShuffleIcon className="size-4 text-pink-400 shrink-0" />
<span className="text-sm font-medium text-pink-200 flex-1 truncate">
@ -19,6 +18,6 @@ export function RandomNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-pink-300/80 line-clamp-2">
{d.description || "Random number generator"}
</div>
</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-fuchsia-950/80 border border-fuchsia-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="sequence" />
<NodeShell nodeType="sequence" className="bg-fuchsia-950/80 border border-fuchsia-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-fuchsia-500/30">
<ListOrderedIcon className="size-4 text-fuchsia-400 shrink-0" />
<span className="text-sm font-medium text-fuchsia-200 flex-1">
Sequence
</span>
<span className="text-sm font-medium text-fuchsia-200 flex-1">Sequence</span>
</div>
<div className="px-3 py-2 flex justify-between text-xs text-fuchsia-300/80">
<span>1</span>
<span>2</span>
<span>3</span>
</div>
</div>
</NodeShell>
);
}

View file

@ -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 (
<div className="bg-teal-950/80 border border-teal-500/50 rounded-lg shadow-md min-w-32">
<NodeHandles nodeType="string" />
<NodeShell nodeType="string" className="bg-teal-950/80 border border-teal-500/50 rounded-lg shadow-md min-w-32">
<div className="flex items-center gap-2 px-3 py-2 border-b border-teal-500/30">
<TypeIcon className="size-4 text-teal-400 shrink-0" />
<span className="text-sm font-medium text-teal-200 flex-1 truncate">
@ -19,6 +18,6 @@ export function StringNode({ data }: NodeProps) {
<div className="px-3 py-2 text-xs text-teal-300/80 line-clamp-2">
{d.description || "String value"}
</div>
</div>
</NodeShell>
);
}

View file

@ -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 (
<div
className="absolute flex items-center gap-1"
style={{
[isInput ? "left" : "right"]: -4,
top: style?.top ?? "50%",
transform: "translateY(-50%)",
flexDirection: isInput ? "row" : "row-reverse",
}}
>
<Handle
type={isInput ? "target" : "source"}
position={isInput ? Position.Left : Position.Right}
id={id}
style={{
background: color,
width: isFlow ? 8 : 6,
height: isFlow ? 8 : 6,
borderRadius: isFlow ? 2 : "50%",
border: `1.5px solid ${color}60`,
position: "relative",
left: "auto",
right: "auto",
top: "auto",
}}
/>
{label && (
<span
className="text-[8px] font-mono leading-none whitespace-nowrap pointer-events-none opacity-70"
style={{ color }}
>
{label}
</span>
)}
</div>
);
}

View file

@ -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 (
<div className="bg-yellow-950/80 border border-yellow-500/50 rounded-lg shadow-md min-w-48">
<NodeHandles nodeType="wait" />
<NodeShell nodeType="wait" className="bg-yellow-950/80 border border-yellow-500/50 rounded-lg shadow-md min-w-48">
<div className="flex items-center gap-2 px-3 py-2 border-b border-yellow-500/30">
<TimerIcon className="size-4 text-yellow-400 shrink-0" />
<span className="text-sm font-medium text-yellow-200 flex-1 truncate">
@ -21,6 +20,6 @@ export function WaitNode({ data }: NodeProps) {
? `Input: ${String(d.input).substring(0, 50)}${String(d.input).length > 50 ? "..." : ""}`
: "No input"}
</div>
</div>
</NodeShell>
);
}