@@ -32,6 +32,8 @@ import { TagIcon } from '../icons/TagIcon';
3232import { CubeIcon } from '../icons/CubeIcon' ;
3333import { ChevronsUpIcon } from '../icons/ChevronsUpIcon' ;
3434import { ChevronsDownIcon } from '../icons/ChevronsDownIcon' ;
35+ import { ChevronDownIcon } from '../icons/ChevronDownIcon' ;
36+ import { ChevronRightIcon } from '../icons/ChevronRightIcon' ;
3537import ReactMarkdown from 'react-markdown' ;
3638import remarkGfm from 'remark-gfm' ;
3739import { PencilIcon } from '../icons/PencilIcon' ;
@@ -131,6 +133,19 @@ const STEP_DEFINITIONS: Record<TaskStepType, { label: string; icon: React.Compon
131133 [ TaskStepType . DOCKER_COMPOSE_BUILD ] : { label : 'Docker: Compose Build' , icon : DockerIcon , description : 'Build or rebuild services with Docker Compose.' } ,
132134} ;
133135
136+ const STEPS_WITH_DETAILS = new Set < TaskStepType > ( [
137+ TaskStepType . GitCheckout ,
138+ TaskStepType . SvnSwitch ,
139+ TaskStepType . DelphiBuild ,
140+ TaskStepType . LAZARUS_BUILD ,
141+ TaskStepType . LAZARUS_BUILD_PACKAGE ,
142+ TaskStepType . FPC_TEST_FPCUNIT ,
143+ TaskStepType . DELPHI_PACKAGE_INNO ,
144+ TaskStepType . DELPHI_PACKAGE_NSIS ,
145+ TaskStepType . DELPHI_TEST_DUNITX ,
146+ TaskStepType . RunCommand ,
147+ ] ) ;
148+
134149const STEP_CATEGORIES = [
135150 { name : 'General' , types : [ TaskStepType . RunCommand ] } ,
136151 { name : 'Git' , types : [ TaskStepType . GitPull , TaskStepType . GitFetch , TaskStepType . GitCheckout , TaskStepType . GitStash ] } ,
@@ -217,12 +232,21 @@ const TaskStepItem: React.FC<{
217232 const logger = useLogger ( ) ;
218233 const stepDef = STEP_DEFINITIONS [ step . type ] ;
219234 const isEnabled = step . enabled ?? true ;
235+ const hasDetails = STEPS_WITH_DETAILS . has ( step . type ) ;
236+ const [ isCollapsed , setIsCollapsed ] = useState ( false ) ;
237+ const detailsId = useMemo ( ( ) => `task-step-${ step . id } -details` , [ step . id ] ) ;
220238
221239 // --- HOOKS MOVED TO TOP ---
222240 const toggleTooltip = useTooltip ( isEnabled ? 'Disable Step' : 'Enable Step' ) ;
223241 const duplicateTooltip = useTooltip ( 'Duplicate Step' ) ;
224242 const moveToTopTooltip = useTooltip ( 'Move Step to Top' ) ;
225243 const moveToBottomTooltip = useTooltip ( 'Move Step to Bottom' ) ;
244+
245+ useEffect ( ( ) => {
246+ if ( ! hasDetails ) {
247+ setIsCollapsed ( false ) ;
248+ }
249+ } , [ hasDetails ] ) ;
226250
227251 const selectedDelphiProject = useMemo ( ( ) => {
228252 return projectInfo ?. delphi ?. projects . find ( p => p . path === step . delphiProjectFile ) ;
@@ -301,47 +325,8 @@ const TaskStepItem: React.FC<{
301325 </ div >
302326 ) ;
303327
304- return (
305- < div className = { `bg-white dark:bg-gray-800/50 p-3 rounded-lg border border-gray-200 dark:border-gray-700 space-y-2 transition-opacity ${ ! isEnabled ? 'opacity-50' : '' } ` } >
306- < div className = "flex items-center justify-between" >
307- < div className = "flex items-center gap-3" >
308- < Icon className = "h-6 w-6 text-blue-500" />
309- < div >
310- < p className = "font-semibold text-gray-800 dark:text-gray-200" > { label } </ p >
311- < p className = "text-xs text-gray-500" > Step { index + 1 } </ p >
312- </ div >
313- </ div >
314- < div className = "flex items-center space-x-2" >
315- < label { ...toggleTooltip } className = "relative inline-flex items-center cursor-pointer" >
316- < input type = "checkbox" checked = { isEnabled } onChange = { ( e ) => onStepChange ( step . id , { enabled : e . target . checked } ) } className = "sr-only peer" />
317- < div className = "w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500/50 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" > </ div >
318- </ label >
319- < button
320- { ...moveToTopTooltip }
321- type = "button"
322- onClick = { ( ) => onMoveStep ( index , 'top' ) }
323- disabled = { index === 0 }
324- aria-label = "Move step to top"
325- className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
326- >
327- < ChevronsUpIcon className = "h-4 w-4" />
328- </ button >
329- < button type = "button" onClick = { ( ) => onMoveStep ( index , 'up' ) } disabled = { index === 0 } className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowUpIcon className = "h-4 w-4" /> </ button >
330- < button type = "button" onClick = { ( ) => onMoveStep ( index , 'down' ) } disabled = { index === totalSteps - 1 } className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowDownIcon className = "h-4 w-4" /> </ button >
331- < button
332- { ...moveToBottomTooltip }
333- type = "button"
334- onClick = { ( ) => onMoveStep ( index , 'bottom' ) }
335- disabled = { index === totalSteps - 1 }
336- aria-label = "Move step to bottom"
337- className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
338- >
339- < ChevronsDownIcon className = "h-4 w-4" />
340- </ button >
341- < button { ...duplicateTooltip } type = "button" onClick = { ( ) => onDuplicateStep ( index ) } className = "p-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < DocumentDuplicateIcon className = "h-4 w-4" /> </ button >
342- < button type = "button" onClick = { ( ) => onRemoveStep ( step . id ) } className = "p-1.5 text-red-500 hover:bg-red-100 dark:hover:bg-red-900/50 rounded-full" > < TrashIcon className = "h-4 w-4" /> </ button >
343- </ div >
344- </ div >
328+ const detailFields = (
329+ < >
345330 { ( step . type === TaskStepType . GitCheckout || step . type === TaskStepType . SvnSwitch ) && (
346331 < div >
347332 < label className = "text-xs font-medium text-gray-500 dark:text-gray-400" >
@@ -517,7 +502,7 @@ const TaskStepItem: React.FC<{
517502 ( acc [ suggestion . group ] = acc [ suggestion . group ] || [ ] ) . push ( suggestion ) ;
518503 return acc ;
519504 } , { } as Record < string , ProjectSuggestion [ ] > ) ;
520-
505+
521506 const handleSelectChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
522507 const newValue = e . target . value ;
523508 // When a predefined command is selected, update the step.
@@ -538,7 +523,7 @@ const TaskStepItem: React.FC<{
538523 ) ) }
539524 < option value = { CUSTOM_COMMAND_VALUE } > Custom Command...</ option >
540525 </ select >
541-
526+
542527 < textarea
543528 placeholder = { `e.g., npm run build -- --env=production\nUse \${VAR_NAME} for variables.` }
544529 value = { step . command || '' }
@@ -550,6 +535,69 @@ const TaskStepItem: React.FC<{
550535 </ div >
551536 ) ;
552537 } ) ( ) }
538+ </ >
539+ ) ;
540+
541+ return (
542+ < div className = { `bg-white dark:bg-gray-800/50 p-3 rounded-lg border border-gray-200 dark:border-gray-700 space-y-2 transition-opacity ${ ! isEnabled ? 'opacity-50' : '' } ` } >
543+ < div className = "flex items-center justify-between" >
544+ < div className = "flex items-center gap-3" >
545+ { hasDetails && (
546+ < button
547+ type = "button"
548+ onClick = { ( ) => setIsCollapsed ( prev => ! prev ) }
549+ aria-label = { `${ isCollapsed ? 'Expand' : 'Collapse' } step details` }
550+ aria-expanded = { ! isCollapsed }
551+ aria-controls = { detailsId }
552+ className = "p-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
553+ >
554+ { isCollapsed ? < ChevronRightIcon className = "h-4 w-4" /> : < ChevronDownIcon className = "h-4 w-4" /> }
555+ </ button >
556+ ) }
557+ < Icon className = "h-6 w-6 text-blue-500" />
558+ < div >
559+ < p className = "font-semibold text-gray-800 dark:text-gray-200" > { label } </ p >
560+ < p className = "text-xs text-gray-500" > Step { index + 1 } </ p >
561+ </ div >
562+ </ div >
563+ < div className = "flex items-center space-x-2" >
564+ < label { ...toggleTooltip } className = "relative inline-flex items-center cursor-pointer" >
565+ < input type = "checkbox" checked = { isEnabled } onChange = { ( e ) => onStepChange ( step . id , { enabled : e . target . checked } ) } className = "sr-only peer" />
566+ < div className = "w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500/50 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" > </ div >
567+ </ label >
568+ < button
569+ { ...moveToTopTooltip }
570+ type = "button"
571+ onClick = { ( ) => onMoveStep ( index , 'top' ) }
572+ disabled = { index === 0 }
573+ aria-label = "Move step to top"
574+ className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
575+ >
576+ < ChevronsUpIcon className = "h-4 w-4" />
577+ </ button >
578+ < button type = "button" onClick = { ( ) => onMoveStep ( index , 'up' ) } disabled = { index === 0 } className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowUpIcon className = "h-4 w-4" /> </ button >
579+ < button type = "button" onClick = { ( ) => onMoveStep ( index , 'down' ) } disabled = { index === totalSteps - 1 } className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowDownIcon className = "h-4 w-4" /> </ button >
580+ < button
581+ { ...moveToBottomTooltip }
582+ type = "button"
583+ onClick = { ( ) => onMoveStep ( index , 'bottom' ) }
584+ disabled = { index === totalSteps - 1 }
585+ aria-label = "Move step to bottom"
586+ className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
587+ >
588+ < ChevronsDownIcon className = "h-4 w-4" />
589+ </ button >
590+ < button { ...duplicateTooltip } type = "button" onClick = { ( ) => onDuplicateStep ( index ) } className = "p-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < DocumentDuplicateIcon className = "h-4 w-4" /> </ button >
591+ < button type = "button" onClick = { ( ) => onRemoveStep ( step . id ) } className = "p-1.5 text-red-500 hover:bg-red-100 dark:hover:bg-red-900/50 rounded-full" > < TrashIcon className = "h-4 w-4" /> </ button >
592+ </ div >
593+ </ div >
594+ { hasDetails ? (
595+ < div id = { detailsId } className = { `mt-3 space-y-3 ${ isCollapsed ? 'hidden' : '' } ` } >
596+ { detailFields }
597+ </ div >
598+ ) : (
599+ detailFields
600+ ) }
553601 </ div >
554602 ) ;
555603} ;
0 commit comments