Browse Source

style: update ArrayNode layout to match new design language

Replace old input/select styling with bg-[#1a1a1a] rounded-md pattern,
switch grid layouts to flex, add rotating chevron for Advanced toggle,
tighten parsed items container and auto-resize formula, and update
default dimensions from 360x360 to 340x260 for a more compact node.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
dee900b857
  1. 118
      src/components/nodes/ArrayNode.tsx
  2. 2
      src/store/utils/nodeDefaults.ts

118
src/components/nodes/ArrayNode.tsx

@ -156,9 +156,10 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
// Auto-resize node height to fit all parsed lines so users don't need to scroll.
useEffect(() => {
const baseHeight = 360;
const perItemHeight = 30;
const newHeight = Math.max(baseHeight, 270 + previewItems.length * perItemHeight);
const headerHeight = 180;
const perItemHeight = 28;
const itemsMinHeight = 60;
const newHeight = headerHeight + Math.max(itemsMinHeight, previewItems.length * perItemHeight + 8);
setNodes((nodes) =>
nodes.map((node) => {
@ -174,8 +175,8 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
id={id}
selected={selected}
hasError={!!nodeData.error}
minWidth={320}
minHeight={300}
minWidth={300}
minHeight={220}
>
<Handle type="target" position={Position.Left} id="text" data-handletype="text" />
@ -187,7 +188,7 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
type="button"
onClick={handleAutoRouteToPrompts}
disabled={previewItems.length === 0}
className="nodrag nopan absolute top-2 right-2 z-10 shrink-0 p-1 rounded border border-neutral-600 bg-neutral-800/90 text-neutral-300 hover:bg-neutral-700 hover:text-neutral-100 disabled:opacity-50 disabled:cursor-not-allowed"
className="nodrag nopan absolute top-2 right-2 z-10 shrink-0 p-1 bg-[#1a1a1a] rounded-md text-neutral-400 hover:text-neutral-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Auto-route to Prompts"
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@ -196,12 +197,12 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
</button>
<div className="flex flex-col gap-2 flex-1 min-h-0">
<div className="grid grid-cols-[auto_1fr] gap-2 items-center">
<label className="text-[11px] text-neutral-400">Split</label>
<div className="flex items-center gap-2">
<label className="shrink-0 text-[11px] text-neutral-400">Split</label>
<select
value={nodeData.splitMode}
onChange={handleBasicModeChange}
className="nodrag nopan bg-neutral-900 border border-neutral-700 rounded px-2 py-1 text-[11px] text-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-600"
className="nodrag nopan flex-1 min-w-0 text-[11px] py-1 px-2 bg-[#1a1a1a] rounded-md focus:outline-none focus:ring-1 focus:ring-neutral-600 text-white"
>
<option value="delimiter">Delimiter</option>
<option value="newline">Newline</option>
@ -210,69 +211,69 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
</div>
{nodeData.splitMode === "delimiter" && (
<div className="grid grid-cols-[auto_1fr] gap-2 items-center">
<label className="text-[11px] text-neutral-400">By</label>
<div className="flex items-center gap-2">
<label className="shrink-0 text-[11px] text-neutral-400">By</label>
<input
value={nodeData.delimiter}
onChange={(e) => updateNodeData(id, { delimiter: e.target.value })}
placeholder="*"
className="nodrag nopan bg-neutral-900 border border-neutral-700 rounded px-2 py-1 text-[11px] text-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-600"
className="nodrag nopan flex-1 min-w-0 text-[11px] py-1 px-2 bg-[#1a1a1a] rounded-md focus:outline-none focus:ring-1 focus:ring-neutral-600 text-white"
/>
</div>
)}
{nodeData.splitMode === "regex" && (
<div className="grid grid-cols-[auto_1fr] gap-2 items-center">
<label className="text-[11px] text-neutral-400">By</label>
<div className="flex items-center gap-2">
<label className="shrink-0 text-[11px] text-neutral-400">By</label>
<input
value={nodeData.regexPattern}
onChange={(e) => updateNodeData(id, { regexPattern: e.target.value })}
placeholder="/\\n+/"
className="nodrag nopan bg-neutral-900 border border-neutral-700 rounded px-2 py-1 text-[11px] text-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-600"
className="nodrag nopan flex-1 min-w-0 text-[11px] py-1 px-2 bg-[#1a1a1a] rounded-md focus:outline-none focus:ring-1 focus:ring-neutral-600 text-white"
/>
</div>
)}
<div className="border border-neutral-700 rounded">
<div>
<button
type="button"
onClick={() => setShowAdvanced((v) => !v)}
className="nodrag nopan w-full flex items-center justify-between px-2 py-1 text-[11px] text-neutral-300 hover:bg-neutral-800/50"
className="nodrag nopan flex items-center gap-1 text-[11px] text-neutral-500 hover:text-neutral-300 transition-colors"
>
<svg className={`w-3 h-3 transition-transform ${showAdvanced ? "rotate-90" : ""}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
<span>Advanced</span>
<span className="text-[10px] text-neutral-500">{showAdvanced ? "Hide" : "Show"}</span>
</button>
{showAdvanced && (
<div className="px-2 pb-2 pt-1 flex flex-col gap-2 border-t border-neutral-700">
<div className="flex items-center gap-3">
<label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
<input
type="checkbox"
checked={nodeData.trimItems}
onChange={(e) => updateNodeData(id, { trimItems: e.target.checked })}
className="nodrag nopan w-3 h-3"
/>
Trim
</label>
<label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
<input
type="checkbox"
checked={nodeData.removeEmpty}
onChange={(e) => updateNodeData(id, { removeEmpty: e.target.checked })}
className="nodrag nopan w-3 h-3"
/>
Remove empty
</label>
</div>
<div className="px-2 pt-1.5 pb-0.5 flex items-center gap-3">
<label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
<input
type="checkbox"
checked={nodeData.trimItems}
onChange={(e) => updateNodeData(id, { trimItems: e.target.checked })}
className="nodrag nopan w-3 h-3 rounded bg-[#1a1a1a] text-neutral-600 focus:ring-1 focus:ring-neutral-600 focus:ring-offset-0"
/>
Trim
</label>
<label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
<input
type="checkbox"
checked={nodeData.removeEmpty}
onChange={(e) => updateNodeData(id, { removeEmpty: e.target.checked })}
className="nodrag nopan w-3 h-3 rounded bg-[#1a1a1a] text-neutral-600 focus:ring-1 focus:ring-neutral-600 focus:ring-offset-0"
/>
Remove empty
</label>
</div>
)}
</div>
<div className="text-[10px] uppercase tracking-wide text-neutral-500">
<div className="mt-1 text-[10px] uppercase tracking-wide text-neutral-500">
Parsed Items ({previewItems.length})
</div>
<div className="relative flex-1 min-h-[90px] border border-neutral-700 rounded bg-neutral-900/40">
<div className="relative min-h-[50px] border border-neutral-700/40 rounded-md bg-[#1a1a1a]">
{nodeData.error ? (
<div className="p-2 text-[11px] text-red-400">{nodeData.error}</div>
) : previewItems.length === 0 ? (
@ -282,24 +283,23 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
{previewItems.map((item, index) => {
const isSelected = nodeData.selectedOutputIndex === index;
return (
<div key={`${index}-${item}`} className="relative pr-8">
<button
type="button"
onClick={() =>
updateNodeData(id, {
selectedOutputIndex: isSelected ? null : index,
})
}
className={`nodrag nopan w-[calc(100%-1rem)] mx-2 my-1 rounded border px-2 py-1 text-[11px] text-left truncate transition-colors ${
isSelected
? "border-blue-500 bg-blue-900/40 text-blue-200"
: "border-neutral-700 bg-neutral-900/80 text-neutral-200 hover:bg-neutral-800"
}`}
title={isSelected ? "Selected for next connection (click to unselect)" : "Click to select for next connection"}
>
{index + 1}. {item}
</button>
</div>
<button
key={`${index}-${item}`}
type="button"
onClick={() =>
updateNodeData(id, {
selectedOutputIndex: isSelected ? null : index,
})
}
className={`nodrag nopan w-[calc(100%-1rem)] mx-2 my-0.5 rounded-md px-2 py-1 text-[11px] text-left truncate transition-colors ${
isSelected
? "bg-blue-900/40 text-blue-200 ring-1 ring-blue-500/60"
: "bg-neutral-800/60 text-neutral-300 hover:bg-neutral-700/60"
}`}
title={isSelected ? "Selected for next connection (click to unselect)" : "Click to select for next connection"}
>
{index + 1}. {item}
</button>
);
})}
</div>

2
src/store/utils/nodeDefaults.ts

@ -39,7 +39,7 @@ export const defaultNodeDimensions: Record<NodeType, { width: number; height: nu
audioInput: { width: 300, height: 200 },
annotation: { width: 300, height: 280 },
prompt: { width: 320, height: 220 },
array: { width: 360, height: 360 },
array: { width: 340, height: 260 },
promptConstructor: { width: 340, height: 280 },
nanoBanana: { width: 300, height: 300 },
generateVideo: { width: 300, height: 300 },

Loading…
Cancel
Save