Browse Source

feat: add visual indicators for optional input nodes

- ImageInputNode: dashed border + "Optional" text when empty, badge when loaded
- AudioInputNode: same pattern as ImageInputNode
- PromptNode: updated placeholder text when isOptional is set

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
5271c1559b
  1. 9
      src/components/nodes/AudioInputNode.tsx
  2. 9
      src/components/nodes/ImageInputNode.tsx
  3. 2
      src/components/nodes/PromptNode.tsx

9
src/components/nodes/AudioInputNode.tsx

@ -143,6 +143,11 @@ export function AudioInputNode({ id, data, selected }: NodeProps<AudioInputNodeT
{nodeData.audioFile ? (
<div className="relative group flex-1 flex flex-col min-h-0 gap-2">
{nodeData.isOptional && (
<span className="absolute top-1 left-1 z-10 text-[9px] font-medium text-neutral-300 bg-black/50 px-1.5 py-0.5 rounded">
Optional
</span>
)}
{/* Filename and duration */}
<div className="flex items-center justify-between shrink-0">
<span className="text-[10px] text-neutral-400 truncate max-w-[150px]" title={nodeData.filename || ""}>
@ -223,13 +228,13 @@ export function AudioInputNode({ id, data, selected }: NodeProps<AudioInputNodeT
onClick={() => fileInputRef.current?.click()}
onDrop={handleDrop}
onDragOver={handleDragOver}
className="w-full h-full bg-neutral-900/40 flex flex-col items-center justify-center cursor-pointer hover:bg-neutral-800/60 transition-colors"
className={`w-full h-full bg-neutral-900/40 flex flex-col items-center justify-center cursor-pointer hover:bg-neutral-800/60 transition-colors ${nodeData.isOptional ? "border-2 border-dashed border-neutral-600" : ""}`}
>
<svg className="w-8 h-8 text-neutral-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M9 9l10.5-3m0 6.553v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 11-.99-3.467l2.31-.66a2.25 2.25 0 001.632-2.163zm0 0V2.25L9 5.25v10.303m0 0v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 01-.99-3.467l2.31-.66A2.25 2.25 0 009 15.553z" />
</svg>
<span className="text-xs text-neutral-500 mt-2">
Drop audio or click
{nodeData.isOptional ? "Optional" : "Drop audio or click"}
</span>
</div>
)}

9
src/components/nodes/ImageInputNode.tsx

@ -106,6 +106,11 @@ export function ImageInputNode({ id, data, selected }: NodeProps<ImageInputNodeT
alt={nodeData.filename || "Uploaded image"}
className="w-full h-full object-cover rounded-lg"
/>
{nodeData.isOptional && (
<span className="absolute bottom-2 left-2 text-[9px] font-medium text-neutral-300 bg-black/50 px-1.5 py-0.5 rounded">
Optional
</span>
)}
<button
onClick={handleRemove}
aria-label="Remove image"
@ -130,12 +135,12 @@ export function ImageInputNode({ id, data, selected }: NodeProps<ImageInputNodeT
}}
onDrop={handleDrop}
onDragOver={handleDragOver}
className="w-full h-full bg-neutral-900/40 flex flex-col items-center justify-center cursor-pointer hover:bg-neutral-900/60 transition-colors"
className={`w-full h-full bg-neutral-900/40 flex flex-col items-center justify-center cursor-pointer hover:bg-neutral-900/60 transition-colors ${nodeData.isOptional ? "border-2 border-dashed border-neutral-600" : ""}`}
>
<svg className="w-8 h-8 text-neutral-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
<span className="text-xs text-neutral-500 mt-2">Drop image</span>
<span className="text-xs text-neutral-500 mt-2">{nodeData.isOptional ? "Optional" : "Drop image"}</span>
</div>
)}

2
src/components/nodes/PromptNode.tsx

@ -109,7 +109,7 @@ export function PromptNode({ id, data, selected }: NodeProps<PromptNodeType>) {
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={hasIncomingTextConnection ? "Text from connected node (editable)..." : "Describe what to generate..."}
placeholder={hasIncomingTextConnection ? "Text from connected node (editable)..." : nodeData.isOptional ? "Optional prompt (leave empty to skip)..." : "Describe what to generate..."}
className="nodrag nopan nowheel w-full h-full p-3 pb-7 text-xs leading-relaxed text-neutral-100 bg-neutral-800 rounded-t-lg resize-none focus:outline-none placeholder:text-neutral-500"
/>
<div className="absolute bottom-0 left-0 right-0 z-10 px-3 py-1.5 bg-neutral-900/90 rounded-b-lg">

Loading…
Cancel
Save