Skip to content

Commit 739341b

Browse files
authored
improvement(router): add resizable textareas for router conditions (#2888)
1 parent 3c43779 commit 739341b

File tree

2 files changed

+117
-41
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components

2 files changed

+117
-41
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/condition-input/condition-input.tsx

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ReactElement } from 'react'
22
import { useEffect, useRef, useState } from 'react'
33
import { createLogger } from '@sim/logger'
4-
import { ChevronDown, ChevronUp, Plus } from 'lucide-react'
4+
import { ChevronDown, ChevronsUpDown, ChevronUp, Plus } from 'lucide-react'
55
import { useParams } from 'next/navigation'
66
import Editor from 'react-simple-code-editor'
77
import { useUpdateNodeInternals } from 'reactflow'
@@ -39,6 +39,16 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
3939

4040
const logger = createLogger('ConditionInput')
4141

42+
/**
43+
* Default height for router textareas in pixels
44+
*/
45+
const ROUTER_DEFAULT_HEIGHT_PX = 100
46+
47+
/**
48+
* Minimum height for router textareas in pixels
49+
*/
50+
const ROUTER_MIN_HEIGHT_PX = 80
51+
4252
/**
4353
* Represents a single conditional block (if/else if/else).
4454
*/
@@ -743,6 +753,61 @@ export function ConditionInput({
743753
}
744754
}, [conditionalBlocks, isRouterMode])
745755

756+
// State for tracking individual router textarea heights
757+
const [routerHeights, setRouterHeights] = useState<{ [key: string]: number }>({})
758+
const isResizing = useRef(false)
759+
760+
/**
761+
* Gets the height for a specific router block, returning default if not set.
762+
*
763+
* @param blockId - ID of the router block
764+
* @returns Height in pixels
765+
*/
766+
const getRouterHeight = (blockId: string): number => {
767+
return routerHeights[blockId] ?? ROUTER_DEFAULT_HEIGHT_PX
768+
}
769+
770+
/**
771+
* Handles mouse-based resize for router textareas.
772+
*
773+
* @param e - Mouse event from the resize handle
774+
* @param blockId - ID of the block being resized
775+
*/
776+
const startRouterResize = (e: React.MouseEvent, blockId: string) => {
777+
if (isPreview || disabled) return
778+
e.preventDefault()
779+
e.stopPropagation()
780+
isResizing.current = true
781+
782+
const startY = e.clientY
783+
const startHeight = getRouterHeight(blockId)
784+
785+
const handleMouseMove = (moveEvent: MouseEvent) => {
786+
if (!isResizing.current) return
787+
788+
const deltaY = moveEvent.clientY - startY
789+
const newHeight = Math.max(ROUTER_MIN_HEIGHT_PX, startHeight + deltaY)
790+
791+
// Update the textarea height directly for smooth resizing
792+
const textarea = inputRefs.current.get(blockId)
793+
if (textarea) {
794+
textarea.style.height = `${newHeight}px`
795+
}
796+
797+
// Update state to keep track
798+
setRouterHeights((prev) => ({ ...prev, [blockId]: newHeight }))
799+
}
800+
801+
const handleMouseUp = () => {
802+
isResizing.current = false
803+
document.removeEventListener('mousemove', handleMouseMove)
804+
document.removeEventListener('mouseup', handleMouseUp)
805+
}
806+
807+
document.addEventListener('mousemove', handleMouseMove)
808+
document.addEventListener('mouseup', handleMouseUp)
809+
}
810+
746811
// Show loading or empty state if not ready or no blocks
747812
if (!isReady || conditionalBlocks.length === 0) {
748813
return (
@@ -907,10 +972,24 @@ export function ConditionInput({
907972
}}
908973
placeholder='Describe when this route should be taken...'
909974
disabled={disabled || isPreview}
910-
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'
911-
rows={2}
975+
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'
976+
rows={4}
977+
style={{ height: `${getRouterHeight(block.id)}px` }}
912978
/>
913979

980+
{/* Custom resize handle */}
981+
{!isPreview && !disabled && (
982+
<div
983+
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)]'
984+
onMouseDown={(e) => startRouterResize(e, block.id)}
985+
onDragStart={(e) => {
986+
e.preventDefault()
987+
}}
988+
>
989+
<ChevronsUpDown className='h-3 w-3 text-[var(--text-muted)]' />
990+
</div>
991+
)}
992+
914993
{block.showEnvVars && (
915994
<EnvVarDropdown
916995
visible={block.showEnvVars}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/long-input/long-input.tsx

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -234,48 +234,45 @@ export function LongInput({
234234
}, [value])
235235

236236
// Handle resize functionality
237-
const startResize = useCallback(
238-
(e: React.MouseEvent) => {
239-
e.preventDefault()
240-
e.stopPropagation()
241-
isResizing.current = true
242-
243-
const startY = e.clientY
244-
const startHeight = height
245-
246-
const handleMouseMove = (moveEvent: MouseEvent) => {
247-
if (!isResizing.current) return
248-
249-
const deltaY = moveEvent.clientY - startY
250-
const newHeight = Math.max(MIN_HEIGHT_PX, startHeight + deltaY)
251-
252-
if (textareaRef.current && overlayRef.current) {
253-
textareaRef.current.style.height = `${newHeight}px`
254-
overlayRef.current.style.height = `${newHeight}px`
255-
}
256-
if (containerRef.current) {
257-
containerRef.current.style.height = `${newHeight}px`
258-
}
259-
// Keep React state in sync so parent layouts (e.g., Editor) update during drag
260-
setHeight(newHeight)
261-
}
237+
const startResize = (e: React.MouseEvent) => {
238+
e.preventDefault()
239+
e.stopPropagation()
240+
isResizing.current = true
241+
242+
const startY = e.clientY
243+
const startHeight = height
244+
245+
const handleMouseMove = (moveEvent: MouseEvent) => {
246+
if (!isResizing.current) return
262247

263-
const handleMouseUp = () => {
264-
if (textareaRef.current) {
265-
const finalHeight = Number.parseInt(textareaRef.current.style.height, 10) || height
266-
setHeight(finalHeight)
267-
}
248+
const deltaY = moveEvent.clientY - startY
249+
const newHeight = Math.max(MIN_HEIGHT_PX, startHeight + deltaY)
268250

269-
isResizing.current = false
270-
document.removeEventListener('mousemove', handleMouseMove)
271-
document.removeEventListener('mouseup', handleMouseUp)
251+
if (textareaRef.current && overlayRef.current) {
252+
textareaRef.current.style.height = `${newHeight}px`
253+
overlayRef.current.style.height = `${newHeight}px`
272254
}
255+
if (containerRef.current) {
256+
containerRef.current.style.height = `${newHeight}px`
257+
}
258+
// Keep React state in sync so parent layouts (e.g., Editor) update during drag
259+
setHeight(newHeight)
260+
}
273261

274-
document.addEventListener('mousemove', handleMouseMove)
275-
document.addEventListener('mouseup', handleMouseUp)
276-
},
277-
[height]
278-
)
262+
const handleMouseUp = () => {
263+
if (textareaRef.current) {
264+
const finalHeight = Number.parseInt(textareaRef.current.style.height, 10) || height
265+
setHeight(finalHeight)
266+
}
267+
268+
isResizing.current = false
269+
document.removeEventListener('mousemove', handleMouseMove)
270+
document.removeEventListener('mouseup', handleMouseUp)
271+
}
272+
273+
document.addEventListener('mousemove', handleMouseMove)
274+
document.addEventListener('mouseup', handleMouseUp)
275+
}
279276

280277
// Expose wand control handlers to parent via ref
281278
useImperativeHandle(

0 commit comments

Comments
 (0)