improvement(router): add resizable textareas for router conditions (#2888)

This commit is contained in:
Waleed
2026-01-19 13:59:13 -08:00
committed by GitHub
parent 3c43779ba3
commit 739341b08e
2 changed files with 114 additions and 38 deletions
@@ -1,7 +1,7 @@
import type { ReactElement } from 'react'
import { useEffect, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { ChevronDown, ChevronUp, Plus } from 'lucide-react'
import { ChevronDown, ChevronsUpDown, ChevronUp, Plus } from 'lucide-react'
import { useParams } from 'next/navigation'
import Editor from 'react-simple-code-editor'
import { useUpdateNodeInternals } from 'reactflow'
@@ -39,6 +39,16 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
const logger = createLogger('ConditionInput')
/**
* Default height for router textareas in pixels
*/
const ROUTER_DEFAULT_HEIGHT_PX = 100
/**
* Minimum height for router textareas in pixels
*/
const ROUTER_MIN_HEIGHT_PX = 80
/**
* Represents a single conditional block (if/else if/else).
*/
@@ -743,6 +753,61 @@ export function ConditionInput({
}
}, [conditionalBlocks, isRouterMode])
// State for tracking individual router textarea heights
const [routerHeights, setRouterHeights] = useState<{ [key: string]: number }>({})
const isResizing = useRef(false)
/**
* Gets the height for a specific router block, returning default if not set.
*
* @param blockId - ID of the router block
* @returns Height in pixels
*/
const getRouterHeight = (blockId: string): number => {
return routerHeights[blockId] ?? ROUTER_DEFAULT_HEIGHT_PX
}
/**
* Handles mouse-based resize for router textareas.
*
* @param e - Mouse event from the resize handle
* @param blockId - ID of the block being resized
*/
const startRouterResize = (e: React.MouseEvent, blockId: string) => {
if (isPreview || disabled) return
e.preventDefault()
e.stopPropagation()
isResizing.current = true
const startY = e.clientY
const startHeight = getRouterHeight(blockId)
const handleMouseMove = (moveEvent: MouseEvent) => {
if (!isResizing.current) return
const deltaY = moveEvent.clientY - startY
const newHeight = Math.max(ROUTER_MIN_HEIGHT_PX, startHeight + deltaY)
// Update the textarea height directly for smooth resizing
const textarea = inputRefs.current.get(blockId)
if (textarea) {
textarea.style.height = `${newHeight}px`
}
// Update state to keep track
setRouterHeights((prev) => ({ ...prev, [blockId]: newHeight }))
}
const handleMouseUp = () => {
isResizing.current = false
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
}
// Show loading or empty state if not ready or no blocks
if (!isReady || conditionalBlocks.length === 0) {
return (
@@ -907,10 +972,24 @@ export function ConditionInput({
}}
placeholder='Describe when this route should be taken...'
disabled={disabled || isPreview}
className='min-h-[60px] resize-none rounded-none border-0 px-3 py-2 text-sm placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
rows={2}
className='min-h-[100px] resize-none rounded-none border-0 px-3 py-2 text-sm placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0'
rows={4}
style={{ height: `${getRouterHeight(block.id)}px` }}
/>
{/* Custom resize handle */}
{!isPreview && !disabled && (
<div
className='absolute right-1 bottom-1 flex h-4 w-4 cursor-ns-resize items-center justify-center rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] dark:bg-[var(--surface-5)]'
onMouseDown={(e) => startRouterResize(e, block.id)}
onDragStart={(e) => {
e.preventDefault()
}}
>
<ChevronsUpDown className='h-3 w-3 text-[var(--text-muted)]' />
</div>
)}
{block.showEnvVars && (
<EnvVarDropdown
visible={block.showEnvVars}
@@ -234,48 +234,45 @@ export function LongInput({
}, [value])
// Handle resize functionality
const startResize = useCallback(
(e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
isResizing.current = true
const startResize = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
isResizing.current = true
const startY = e.clientY
const startHeight = height
const startY = e.clientY
const startHeight = height
const handleMouseMove = (moveEvent: MouseEvent) => {
if (!isResizing.current) return
const handleMouseMove = (moveEvent: MouseEvent) => {
if (!isResizing.current) return
const deltaY = moveEvent.clientY - startY
const newHeight = Math.max(MIN_HEIGHT_PX, startHeight + deltaY)
const deltaY = moveEvent.clientY - startY
const newHeight = Math.max(MIN_HEIGHT_PX, startHeight + deltaY)
if (textareaRef.current && overlayRef.current) {
textareaRef.current.style.height = `${newHeight}px`
overlayRef.current.style.height = `${newHeight}px`
}
if (containerRef.current) {
containerRef.current.style.height = `${newHeight}px`
}
// Keep React state in sync so parent layouts (e.g., Editor) update during drag
setHeight(newHeight)
if (textareaRef.current && overlayRef.current) {
textareaRef.current.style.height = `${newHeight}px`
overlayRef.current.style.height = `${newHeight}px`
}
if (containerRef.current) {
containerRef.current.style.height = `${newHeight}px`
}
// Keep React state in sync so parent layouts (e.g., Editor) update during drag
setHeight(newHeight)
}
const handleMouseUp = () => {
if (textareaRef.current) {
const finalHeight = Number.parseInt(textareaRef.current.style.height, 10) || height
setHeight(finalHeight)
}
const handleMouseUp = () => {
if (textareaRef.current) {
const finalHeight = Number.parseInt(textareaRef.current.style.height, 10) || height
setHeight(finalHeight)
}
isResizing.current = false
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
isResizing.current = false
document.removeEventListener('mousemove', handleMouseMove)
document.removeEventListener('mouseup', handleMouseUp)
}
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
},
[height]
)
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
}
// Expose wand control handlers to parent via ref
useImperativeHandle(