@@ -12,6 +12,7 @@ import {
1212 getCodeEditorProps ,
1313 highlight ,
1414 languages ,
15+ Textarea ,
1516 Tooltip ,
1617} from '@/components/emcn'
1718import { Trash } from '@/components/emcn/icons/trash'
@@ -74,6 +75,8 @@ interface ConditionInputProps {
7475 previewValue ?: string | null
7576 /** Whether the component is disabled */
7677 disabled ?: boolean
78+ /** Mode: 'condition' for code editor, 'router' for text input */
79+ mode ?: 'condition' | 'router'
7780}
7881
7982/**
@@ -101,7 +104,9 @@ export function ConditionInput({
101104 isPreview = false ,
102105 previewValue,
103106 disabled = false ,
107+ mode = 'condition' ,
104108} : ConditionInputProps ) {
109+ const isRouterMode = mode === 'router'
105110 const params = useParams ( )
106111 const workspaceId = params . workspaceId as string
107112 const [ storeValue , setStoreValue ] = useSubBlockValue ( blockId , subBlockId )
@@ -161,32 +166,50 @@ export function ConditionInput({
161166 const shouldPersistRef = useRef < boolean > ( false )
162167
163168 /**
164- * Creates default if/else conditional blocks with stable IDs.
169+ * Creates default blocks with stable IDs.
170+ * For conditions: if/else blocks. For router: one route block.
165171 *
166- * @returns Array of two default blocks (if and else)
172+ * @returns Array of default blocks
167173 */
168- const createDefaultBlocks = ( ) : ConditionalBlock [ ] => [
169- {
170- id : generateStableId ( blockId , 'if' ) ,
171- title : 'if' ,
172- value : '' ,
173- showTags : false ,
174- showEnvVars : false ,
175- searchTerm : '' ,
176- cursorPosition : 0 ,
177- activeSourceBlockId : null ,
178- } ,
179- {
180- id : generateStableId ( blockId , 'else' ) ,
181- title : 'else' ,
182- value : '' ,
183- showTags : false ,
184- showEnvVars : false ,
185- searchTerm : '' ,
186- cursorPosition : 0 ,
187- activeSourceBlockId : null ,
188- } ,
189- ]
174+ const createDefaultBlocks = ( ) : ConditionalBlock [ ] => {
175+ if ( isRouterMode ) {
176+ return [
177+ {
178+ id : generateStableId ( blockId , 'route1' ) ,
179+ title : 'route1' ,
180+ value : '' ,
181+ showTags : false ,
182+ showEnvVars : false ,
183+ searchTerm : '' ,
184+ cursorPosition : 0 ,
185+ activeSourceBlockId : null ,
186+ } ,
187+ ]
188+ }
189+
190+ return [
191+ {
192+ id : generateStableId ( blockId , 'if' ) ,
193+ title : 'if' ,
194+ value : '' ,
195+ showTags : false ,
196+ showEnvVars : false ,
197+ searchTerm : '' ,
198+ cursorPosition : 0 ,
199+ activeSourceBlockId : null ,
200+ } ,
201+ {
202+ id : generateStableId ( blockId , 'else' ) ,
203+ title : 'else' ,
204+ value : '' ,
205+ showTags : false ,
206+ showEnvVars : false ,
207+ searchTerm : '' ,
208+ cursorPosition : 0 ,
209+ activeSourceBlockId : null ,
210+ } ,
211+ ]
212+ }
190213
191214 // Initialize with a loading state instead of default blocks
192215 const [ conditionalBlocks , setConditionalBlocks ] = useState < ConditionalBlock [ ] > ( [ ] )
@@ -270,10 +293,13 @@ export function ConditionInput({
270293 const parsedBlocks = safeParseJSON ( effectiveValueStr )
271294
272295 if ( parsedBlocks ) {
273- const blocksWithCorrectTitles = parsedBlocks . map ( ( block , index ) => ( {
274- ...block ,
275- title : index === 0 ? 'if' : index === parsedBlocks . length - 1 ? 'else' : 'else if' ,
276- } ) )
296+ // For router mode, keep original titles. For condition mode, assign if/else if/else
297+ const blocksWithCorrectTitles = isRouterMode
298+ ? parsedBlocks
299+ : parsedBlocks . map ( ( block , index ) => ( {
300+ ...block ,
301+ title : index === 0 ? 'if' : index === parsedBlocks . length - 1 ? 'else' : 'else if' ,
302+ } ) )
277303
278304 setConditionalBlocks ( blocksWithCorrectTitles )
279305 hasInitializedRef . current = true
@@ -573,12 +599,17 @@ export function ConditionInput({
573599
574600 /**
575601 * Updates block titles based on their position in the array.
576- * First block is always 'if', last is 'else', middle ones are 'else if'.
602+ * For conditions: First block is 'if', last is 'else', middle ones are 'else if'.
603+ * For router: Titles are user-editable and not auto-updated.
577604 *
578605 * @param blocks - Array of conditional blocks
579606 * @returns Updated blocks with correct titles
580607 */
581608 const updateBlockTitles = ( blocks : ConditionalBlock [ ] ) : ConditionalBlock [ ] => {
609+ if ( isRouterMode ) {
610+ // For router mode, don't change titles - they're user-editable
611+ return blocks
612+ }
582613 return blocks . map ( ( block , index ) => ( {
583614 ...block ,
584615 title : index === 0 ? 'if' : index === blocks . length - 1 ? 'else' : 'else if' ,
@@ -590,13 +621,15 @@ export function ConditionInput({
590621 if ( isPreview || disabled ) return
591622
592623 const blockIndex = conditionalBlocks . findIndex ( ( block ) => block . id === afterId )
593- if ( conditionalBlocks [ blockIndex ] ?. title === 'else' ) return
624+ if ( ! isRouterMode && conditionalBlocks [ blockIndex ] ?. title === 'else' ) return
594625
595- const newBlockId = generateStableId ( blockId , `else-if-${ Date . now ( ) } ` )
626+ const newBlockId = isRouterMode
627+ ? generateStableId ( blockId , `route-${ Date . now ( ) } ` )
628+ : generateStableId ( blockId , `else-if-${ Date . now ( ) } ` )
596629
597630 const newBlock : ConditionalBlock = {
598631 id : newBlockId ,
599- title : '' ,
632+ title : isRouterMode ? `route- ${ Date . now ( ) } ` : '' ,
600633 value : '' ,
601634 showTags : false ,
602635 showEnvVars : false ,
@@ -710,21 +743,23 @@ export function ConditionInput({
710743 < div
711744 className = { cn (
712745 'flex items-center justify-between overflow-hidden bg-transparent px-[10px] py-[5px]' ,
713- block . title === 'else'
714- ? 'rounded-[4px] border-0'
715- : 'rounded-t-[4px] border-[var(--border-1)] border-b'
746+ isRouterMode
747+ ? 'rounded-t-[4px] border-[var(--border-1)] border-b'
748+ : block . title === 'else'
749+ ? 'rounded-[4px] border-0'
750+ : 'rounded-t-[4px] border-[var(--border-1)] border-b'
716751 ) }
717752 >
718753 < span className = 'font-medium text-[14px] text-[var(--text-tertiary)]' >
719- { block . title }
754+ { isRouterMode ? `Route ${ index + 1 } ` : block . title }
720755 </ span >
721756 < div className = 'flex items-center gap-[8px]' >
722757 < Tooltip . Root >
723758 < Tooltip . Trigger asChild >
724759 < Button
725760 variant = 'ghost'
726761 onClick = { ( ) => addBlock ( block . id ) }
727- disabled = { isPreview || disabled || block . title === 'else' }
762+ disabled = { isPreview || disabled || ( ! isRouterMode && block . title === 'else' ) }
728763 className = 'h-auto p-0'
729764 >
730765 < Plus className = 'h-[14px] w-[14px]' />
@@ -739,7 +774,12 @@ export function ConditionInput({
739774 < Button
740775 variant = 'ghost'
741776 onClick = { ( ) => moveBlock ( block . id , 'up' ) }
742- disabled = { isPreview || index === 0 || disabled || block . title === 'else' }
777+ disabled = {
778+ isPreview ||
779+ index === 0 ||
780+ disabled ||
781+ ( ! isRouterMode && block . title === 'else' )
782+ }
743783 className = 'h-auto p-0'
744784 >
745785 < ChevronUp className = 'h-[14px] w-[14px]' />
@@ -758,8 +798,8 @@ export function ConditionInput({
758798 isPreview ||
759799 disabled ||
760800 index === conditionalBlocks . length - 1 ||
761- conditionalBlocks [ index + 1 ] ?. title === 'else' ||
762- block . title === 'else'
801+ ( ! isRouterMode && conditionalBlocks [ index + 1 ] ?. title === 'else' ) ||
802+ ( ! isRouterMode && block . title === 'else' )
763803 }
764804 className = 'h-auto p-0'
765805 >
@@ -775,18 +815,122 @@ export function ConditionInput({
775815 < Button
776816 variant = 'ghost'
777817 onClick = { ( ) => removeBlock ( block . id ) }
778- disabled = { isPreview || conditionalBlocks . length === 1 || disabled }
818+ disabled = { isPreview || disabled || conditionalBlocks . length === 1 }
779819 className = 'h-auto p-0 text-[var(--text-error)] hover:text-[var(--text-error)]'
780820 >
781821 < Trash className = 'h-[14px] w-[14px]' />
782822 < span className = 'sr-only' > Delete Block</ span >
783823 </ Button >
784824 </ Tooltip . Trigger >
785- < Tooltip . Content > Delete Condition</ Tooltip . Content >
825+ < Tooltip . Content >
826+ { isRouterMode ? 'Delete Route' : 'Delete Condition' }
827+ </ Tooltip . Content >
786828 </ Tooltip . Root >
787829 </ div >
788830 </ div >
789- { block . title !== 'else' &&
831+ { /* Router mode: show description textarea with tag/env var support */ }
832+ { isRouterMode && (
833+ < div
834+ className = 'relative'
835+ onDragOver = { ( e ) => e . preventDefault ( ) }
836+ onDrop = { ( e ) => handleDrop ( block . id , e ) }
837+ >
838+ < Textarea
839+ data-router-block-id = { block . id }
840+ value = { block . value }
841+ onChange = { ( e ) => {
842+ if ( ! isPreview && ! disabled ) {
843+ const newValue = e . target . value
844+ const pos = e . target . selectionStart ?? 0
845+
846+ const tagTrigger = checkTagTrigger ( newValue , pos )
847+ const envVarTrigger = checkEnvVarTrigger ( newValue , pos )
848+
849+ shouldPersistRef . current = true
850+ setConditionalBlocks ( ( blocks ) =>
851+ blocks . map ( ( b ) =>
852+ b . id === block . id
853+ ? {
854+ ...b ,
855+ value : newValue ,
856+ showTags : tagTrigger . show ,
857+ showEnvVars : envVarTrigger . show ,
858+ searchTerm : envVarTrigger . show ? envVarTrigger . searchTerm : '' ,
859+ cursorPosition : pos ,
860+ }
861+ : b
862+ )
863+ )
864+ }
865+ } }
866+ onBlur = { ( ) => {
867+ setTimeout ( ( ) => {
868+ setConditionalBlocks ( ( blocks ) =>
869+ blocks . map ( ( b ) =>
870+ b . id === block . id ? { ...b , showTags : false , showEnvVars : false } : b
871+ )
872+ )
873+ } , 150 )
874+ } }
875+ placeholder = 'Describe when this route should be taken...'
876+ disabled = { disabled || isPreview }
877+ 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'
878+ rows = { 2 }
879+ />
880+
881+ { block . showEnvVars && (
882+ < EnvVarDropdown
883+ visible = { block . showEnvVars }
884+ onSelect = { ( newValue ) => handleEnvVarSelectImmediate ( block . id , newValue ) }
885+ searchTerm = { block . searchTerm }
886+ inputValue = { block . value }
887+ cursorPosition = { block . cursorPosition }
888+ workspaceId = { workspaceId }
889+ onClose = { ( ) => {
890+ setConditionalBlocks ( ( blocks ) =>
891+ blocks . map ( ( b ) =>
892+ b . id === block . id
893+ ? {
894+ ...b ,
895+ showEnvVars : false ,
896+ searchTerm : '' ,
897+ }
898+ : b
899+ )
900+ )
901+ } }
902+ />
903+ ) }
904+
905+ { block . showTags && (
906+ < TagDropdown
907+ visible = { block . showTags }
908+ onSelect = { ( newValue ) => handleTagSelectImmediate ( block . id , newValue ) }
909+ blockId = { blockId }
910+ activeSourceBlockId = { block . activeSourceBlockId }
911+ inputValue = { block . value }
912+ cursorPosition = { block . cursorPosition }
913+ onClose = { ( ) => {
914+ setConditionalBlocks ( ( blocks ) =>
915+ blocks . map ( ( b ) =>
916+ b . id === block . id
917+ ? {
918+ ...b ,
919+ showTags : false ,
920+ activeSourceBlockId : null ,
921+ }
922+ : b
923+ )
924+ )
925+ } }
926+ />
927+ ) }
928+ </ div >
929+ ) }
930+
931+ { /* Condition mode: show code editor */ }
932+ { ! isRouterMode &&
933+ block . title !== 'else' &&
790934 ( ( ) => {
791935 const blockLineCount = block . value . split ( '\n' ) . length
792936 const blockGutterWidth = calculateGutterWidth ( blockLineCount )
0 commit comments