|
1 | 1 | import type { ReactElement } from 'react' |
2 | 2 | import { useEffect, useRef, useState } from 'react' |
3 | 3 | import { createLogger } from '@sim/logger' |
4 | | -import { ChevronDown, ChevronUp, Plus } from 'lucide-react' |
| 4 | +import { ChevronDown, ChevronsUpDown, ChevronUp, Plus } from 'lucide-react' |
5 | 5 | import { useParams } from 'next/navigation' |
6 | 6 | import Editor from 'react-simple-code-editor' |
7 | 7 | import { useUpdateNodeInternals } from 'reactflow' |
@@ -39,6 +39,16 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store' |
39 | 39 |
|
40 | 40 | const logger = createLogger('ConditionInput') |
41 | 41 |
|
| 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 | + |
42 | 52 | /** |
43 | 53 | * Represents a single conditional block (if/else if/else). |
44 | 54 | */ |
@@ -743,6 +753,61 @@ export function ConditionInput({ |
743 | 753 | } |
744 | 754 | }, [conditionalBlocks, isRouterMode]) |
745 | 755 |
|
| 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 | + |
746 | 811 | // Show loading or empty state if not ready or no blocks |
747 | 812 | if (!isReady || conditionalBlocks.length === 0) { |
748 | 813 | return ( |
@@ -907,10 +972,24 @@ export function ConditionInput({ |
907 | 972 | }} |
908 | 973 | placeholder='Describe when this route should be taken...' |
909 | 974 | 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` }} |
912 | 978 | /> |
913 | 979 |
|
| 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 | + |
914 | 993 | {block.showEnvVars && ( |
915 | 994 | <EnvVarDropdown |
916 | 995 | visible={block.showEnvVars} |
|
0 commit comments