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. // Auto-resize node height to fit all parsed lines so users don't need to scroll.
useEffect(() => { useEffect(() => {
const baseHeight = 360; const headerHeight = 180;
const perItemHeight = 30; const perItemHeight = 28;
const newHeight = Math.max(baseHeight, 270 + previewItems.length * perItemHeight); const itemsMinHeight = 60;
const newHeight = headerHeight + Math.max(itemsMinHeight, previewItems.length * perItemHeight + 8);
setNodes((nodes) => setNodes((nodes) =>
nodes.map((node) => { nodes.map((node) => {
@ -174,8 +175,8 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
id={id} id={id}
selected={selected} selected={selected}
hasError={!!nodeData.error} hasError={!!nodeData.error}
minWidth={320} minWidth={300}
minHeight={300} minHeight={220}
> >
<Handle type="target" position={Position.Left} id="text" data-handletype="text" /> <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" type="button"
onClick={handleAutoRouteToPrompts} onClick={handleAutoRouteToPrompts}
disabled={previewItems.length === 0} 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" title="Auto-route to Prompts"
> >
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <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> </button>
<div className="flex flex-col gap-2 flex-1 min-h-0"> <div className="flex flex-col gap-2 flex-1 min-h-0">
<div className="grid grid-cols-[auto_1fr] gap-2 items-center"> <div className="flex items-center gap-2">
<label className="text-[11px] text-neutral-400">Split</label> <label className="shrink-0 text-[11px] text-neutral-400">Split</label>
<select <select
value={nodeData.splitMode} value={nodeData.splitMode}
onChange={handleBasicModeChange} 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="delimiter">Delimiter</option>
<option value="newline">Newline</option> <option value="newline">Newline</option>
@ -210,69 +211,69 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
</div> </div>
{nodeData.splitMode === "delimiter" && ( {nodeData.splitMode === "delimiter" && (
<div className="grid grid-cols-[auto_1fr] gap-2 items-center"> <div className="flex items-center gap-2">
<label className="text-[11px] text-neutral-400">By</label> <label className="shrink-0 text-[11px] text-neutral-400">By</label>
<input <input
value={nodeData.delimiter} value={nodeData.delimiter}
onChange={(e) => updateNodeData(id, { delimiter: e.target.value })} onChange={(e) => updateNodeData(id, { delimiter: e.target.value })}
placeholder="*" 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> </div>
)} )}
{nodeData.splitMode === "regex" && ( {nodeData.splitMode === "regex" && (
<div className="grid grid-cols-[auto_1fr] gap-2 items-center"> <div className="flex items-center gap-2">
<label className="text-[11px] text-neutral-400">By</label> <label className="shrink-0 text-[11px] text-neutral-400">By</label>
<input <input
value={nodeData.regexPattern} value={nodeData.regexPattern}
onChange={(e) => updateNodeData(id, { regexPattern: e.target.value })} onChange={(e) => updateNodeData(id, { regexPattern: e.target.value })}
placeholder="/\\n+/" 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>
)} )}
<div className="border border-neutral-700 rounded"> <div>
<button <button
type="button" type="button"
onClick={() => setShowAdvanced((v) => !v)} 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>Advanced</span>
<span className="text-[10px] text-neutral-500">{showAdvanced ? "Hide" : "Show"}</span>
</button> </button>
{showAdvanced && ( {showAdvanced && (
<div className="px-2 pb-2 pt-1 flex flex-col gap-2 border-t border-neutral-700"> <div className="px-2 pt-1.5 pb-0.5 flex items-center gap-3">
<div className="flex items-center gap-3"> <label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
<label className="flex items-center gap-1.5 text-[11px] text-neutral-300"> <input
<input type="checkbox"
type="checkbox" checked={nodeData.trimItems}
checked={nodeData.trimItems} onChange={(e) => updateNodeData(id, { trimItems: e.target.checked })}
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"
className="nodrag nopan w-3 h-3" />
/> Trim
Trim </label>
</label> <label className="flex items-center gap-1.5 text-[11px] text-neutral-300">
<label className="flex items-center gap-1.5 text-[11px] text-neutral-300"> <input
<input type="checkbox"
type="checkbox" checked={nodeData.removeEmpty}
checked={nodeData.removeEmpty} onChange={(e) => updateNodeData(id, { removeEmpty: e.target.checked })}
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"
className="nodrag nopan w-3 h-3" />
/> Remove empty
Remove empty </label>
</label>
</div>
</div> </div>
)} )}
</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}) Parsed Items ({previewItems.length})
</div> </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 ? ( {nodeData.error ? (
<div className="p-2 text-[11px] text-red-400">{nodeData.error}</div> <div className="p-2 text-[11px] text-red-400">{nodeData.error}</div>
) : previewItems.length === 0 ? ( ) : previewItems.length === 0 ? (
@ -282,24 +283,23 @@ export function ArrayNode({ id, data, selected }: NodeProps<ArrayNodeType>) {
{previewItems.map((item, index) => { {previewItems.map((item, index) => {
const isSelected = nodeData.selectedOutputIndex === index; const isSelected = nodeData.selectedOutputIndex === index;
return ( return (
<div key={`${index}-${item}`} className="relative pr-8"> <button
<button key={`${index}-${item}`}
type="button" type="button"
onClick={() => onClick={() =>
updateNodeData(id, { updateNodeData(id, {
selectedOutputIndex: isSelected ? null : index, 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 ${ 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 isSelected
? "border-blue-500 bg-blue-900/40 text-blue-200" ? "bg-blue-900/40 text-blue-200 ring-1 ring-blue-500/60"
: "border-neutral-700 bg-neutral-900/80 text-neutral-200 hover:bg-neutral-800" : "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"} title={isSelected ? "Selected for next connection (click to unselect)" : "Click to select for next connection"}
> >
{index + 1}. {item} {index + 1}. {item}
</button> </button>
</div>
); );
})} })}
</div> </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 }, audioInput: { width: 300, height: 200 },
annotation: { width: 300, height: 280 }, annotation: { width: 300, height: 280 },
prompt: { width: 320, height: 220 }, prompt: { width: 320, height: 220 },
array: { width: 360, height: 360 }, array: { width: 340, height: 260 },
promptConstructor: { width: 340, height: 280 }, promptConstructor: { width: 340, height: 280 },
nanoBanana: { width: 300, height: 300 }, nanoBanana: { width: 300, height: 300 },
generateVideo: { width: 300, height: 300 }, generateVideo: { width: 300, height: 300 },

Loading…
Cancel
Save