From 468eda2b4d550ebbefbf77ebcf39beb544b2c760 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 19 Jan 2026 13:50:40 -0800 Subject: [PATCH] improvement(router): add resizable textareas for router conditions --- .../condition-input/condition-input.tsx | 85 ++++++++++++++++++- .../components/long-input/long-input.tsx | 73 ++++++++-------- 2 files changed, 117 insertions(+), 41 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx index b37e6ecba1..c614f3662a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx @@ -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 && ( +
startRouterResize(e, block.id)} + onDragStart={(e) => { + e.preventDefault() + }} + > + +
+ )} + {block.showEnvVars && ( { - e.preventDefault() - e.stopPropagation() - isResizing.current = true - - const startY = e.clientY - const startHeight = height - - const handleMouseMove = (moveEvent: MouseEvent) => { - if (!isResizing.current) return - - 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) - } + const startResize = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + isResizing.current = true + + const startY = e.clientY + const startHeight = height + + const handleMouseMove = (moveEvent: MouseEvent) => { + if (!isResizing.current) return - const handleMouseUp = () => { - if (textareaRef.current) { - const finalHeight = Number.parseInt(textareaRef.current.style.height, 10) || height - setHeight(finalHeight) - } + const deltaY = moveEvent.clientY - startY + const newHeight = Math.max(MIN_HEIGHT_PX, startHeight + deltaY) - isResizing.current = false - document.removeEventListener('mousemove', handleMouseMove) - document.removeEventListener('mouseup', handleMouseUp) + 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) + } - document.addEventListener('mousemove', handleMouseMove) - document.addEventListener('mouseup', handleMouseUp) - }, - [height] - ) + 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) + } + + document.addEventListener('mousemove', handleMouseMove) + document.addEventListener('mouseup', handleMouseUp) + } // Expose wand control handlers to parent via ref useImperativeHandle(