Skip to content

Commit ed07662

Browse files
authored
Merge pull request #85 from beNative/codex/enhance-task-designer-ui-with-collapsible-steps
Add collapsible controls to task designer step details
2 parents 6e3b870 + 906f84a commit ed07662

File tree

1 file changed

+108
-58
lines changed

1 file changed

+108
-58
lines changed

components/modals/RepoFormModal.tsx

Lines changed: 108 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { TagIcon } from '../icons/TagIcon';
3232
import { CubeIcon } from '../icons/CubeIcon';
3333
import { ChevronsUpIcon } from '../icons/ChevronsUpIcon';
3434
import { ChevronsDownIcon } from '../icons/ChevronsDownIcon';
35+
import { ChevronDownIcon } from '../icons/ChevronDownIcon';
36+
import { ChevronRightIcon } from '../icons/ChevronRightIcon';
3537
import ReactMarkdown from 'react-markdown';
3638
import remarkGfm from 'remark-gfm';
3739
import { 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+
134149
const 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,71 @@ const TaskStepItem: React.FC<{
550535
</div>
551536
);
552537
})()}
538+
</>
539+
);
540+
541+
return (
542+
<div className={`bg-white dark:bg-gray-800/50 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 space-y-1.5 transition-opacity ${!isEnabled ? 'opacity-50' : ''}`}>
543+
<div className="flex items-center justify-between gap-2.5">
544+
<div className="flex items-center gap-1.5">
545+
<div className="flex h-6 w-6 items-center justify-center">
546+
{hasDetails ? (
547+
<button
548+
type="button"
549+
onClick={() => setIsCollapsed(prev => !prev)}
550+
aria-label={`${isCollapsed ? 'Expand' : 'Collapse'} step details`}
551+
aria-expanded={!isCollapsed}
552+
aria-controls={detailsId}
553+
className="p-0.5 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
554+
>
555+
{isCollapsed ? <ChevronRightIcon className="h-4 w-4" /> : <ChevronDownIcon className="h-4 w-4" />}
556+
</button>
557+
) : null}
558+
</div>
559+
<Icon className="h-6 w-6 text-blue-500" />
560+
<div>
561+
<p className="font-semibold text-gray-800 dark:text-gray-200">{label}</p>
562+
<p className="text-xs text-gray-500">Step {index + 1}</p>
563+
</div>
564+
</div>
565+
<div className="flex items-center space-x-1.5">
566+
<label {...toggleTooltip} className="relative inline-flex items-center cursor-pointer">
567+
<input type="checkbox" checked={isEnabled} onChange={(e) => onStepChange(step.id, {enabled: e.target.checked})} className="sr-only peer" />
568+
<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>
569+
</label>
570+
<button
571+
{...moveToTopTooltip}
572+
type="button"
573+
onClick={() => onMoveStep(index, 'top')}
574+
disabled={index === 0}
575+
aria-label="Move step to top"
576+
className="p-1 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
577+
>
578+
<ChevronsUpIcon className="h-4 w-4" />
579+
</button>
580+
<button type="button" onClick={() => onMoveStep(index, 'up')} disabled={index === 0} className="p-1 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>
581+
<button type="button" onClick={() => onMoveStep(index, 'down')} disabled={index === totalSteps - 1} className="p-1 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>
582+
<button
583+
{...moveToBottomTooltip}
584+
type="button"
585+
onClick={() => onMoveStep(index, 'bottom')}
586+
disabled={index === totalSteps - 1}
587+
aria-label="Move step to bottom"
588+
className="p-1 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
589+
>
590+
<ChevronsDownIcon className="h-4 w-4" />
591+
</button>
592+
<button {...duplicateTooltip} type="button" onClick={() => onDuplicateStep(index)} className="p-1 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>
593+
<button type="button" onClick={() => onRemoveStep(step.id)} className="p-1 text-red-500 hover:bg-red-100 dark:hover:bg-red-900/50 rounded-full"><TrashIcon className="h-4 w-4" /></button>
594+
</div>
595+
</div>
596+
{hasDetails ? (
597+
<div id={detailsId} className={`mt-1.5 space-y-1.5 ${isCollapsed ? 'hidden' : ''}`}>
598+
{detailFields}
599+
</div>
600+
) : (
601+
detailFields
602+
)}
553603
</div>
554604
);
555605
};
@@ -724,7 +774,7 @@ const NodejsTaskGenerator: React.FC<{
724774

725775

726776
return (
727-
<div className="p-3 mb-4 bg-green-50 dark:bg-gray-900/50 rounded-lg border border-green-200 dark:border-gray-700">
777+
<div className="p-3 mb-3 bg-green-50 dark:bg-gray-900/50 rounded-lg border border-green-200 dark:border-gray-700">
728778
<div className="flex items-center gap-2 mb-2">
729779
<NodeIcon className="h-5 w-5 text-green-500"/>
730780
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Node.js Project Detected</h3>
@@ -780,7 +830,7 @@ const GoTaskGenerator: React.FC<{
780830
};
781831

782832
return (
783-
<div className="p-3 mb-4 bg-cyan-50 dark:bg-gray-900/50 rounded-lg border border-cyan-200 dark:border-gray-700">
833+
<div className="p-3 mb-3 bg-cyan-50 dark:bg-gray-900/50 rounded-lg border border-cyan-200 dark:border-gray-700">
784834
<div className="flex items-center gap-2 mb-2">
785835
<CodeBracketIcon className="h-5 w-5 text-cyan-500" />
786836
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Go Project Detected</h3>
@@ -845,7 +895,7 @@ const RustTaskGenerator: React.FC<{
845895
};
846896

847897
return (
848-
<div className="p-3 mb-4 bg-amber-50 dark:bg-gray-900/50 rounded-lg border border-amber-200 dark:border-gray-700">
898+
<div className="p-3 mb-3 bg-amber-50 dark:bg-gray-900/50 rounded-lg border border-amber-200 dark:border-gray-700">
849899
<div className="flex items-center gap-2 mb-2">
850900
<CodeBracketIcon className="h-5 w-5 text-amber-500" />
851901
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Rust Project Detected</h3>
@@ -911,7 +961,7 @@ const MavenTaskGenerator: React.FC<{
911961
};
912962

913963
return (
914-
<div className="p-3 mb-4 bg-orange-50 dark:bg-gray-900/50 rounded-lg border border-orange-200 dark:border-gray-700">
964+
<div className="p-3 mb-3 bg-orange-50 dark:bg-gray-900/50 rounded-lg border border-orange-200 dark:border-gray-700">
915965
<div className="flex items-center gap-2 mb-2">
916966
<DocumentTextIcon className="h-5 w-5 text-orange-500" />
917967
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Maven Project Detected</h3>
@@ -979,7 +1029,7 @@ const DotnetTaskGenerator: React.FC<{
9791029
};
9801030

9811031
return (
982-
<div className="p-3 mb-4 bg-purple-50 dark:bg-gray-900/50 rounded-lg border border-purple-200 dark:border-gray-700">
1032+
<div className="p-3 mb-3 bg-purple-50 dark:bg-gray-900/50 rounded-lg border border-purple-200 dark:border-gray-700">
9831033
<div className="flex items-center gap-2 mb-2">
9841034
<CubeIcon className="h-5 w-5 text-purple-500" />
9851035
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">.NET Project Detected</h3>
@@ -1047,7 +1097,7 @@ const PythonTaskGenerator: React.FC<{
10471097

10481098

10491099
return (
1050-
<div className="p-3 mb-4 bg-blue-50 dark:bg-gray-900/50 rounded-lg border border-blue-200 dark:border-gray-700">
1100+
<div className="p-3 mb-3 bg-blue-50 dark:bg-gray-900/50 rounded-lg border border-blue-200 dark:border-gray-700">
10511101
<div className="flex items-center gap-2 mb-2">
10521102
<PythonIcon className="h-5 w-5 text-blue-500"/>
10531103
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Python Project Detected</h3>
@@ -1120,7 +1170,7 @@ const DockerTaskGenerator: React.FC<{
11201170
];
11211171

11221172
return (
1123-
<div className="p-3 mb-4 bg-sky-50 dark:bg-gray-900/50 rounded-lg border border-sky-200 dark:border-gray-700">
1173+
<div className="p-3 mb-3 bg-sky-50 dark:bg-gray-900/50 rounded-lg border border-sky-200 dark:border-gray-700">
11241174
<div className="flex items-center gap-2 mb-2">
11251175
<DockerIcon className="h-5 w-5 text-sky-500" />
11261176
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Docker Artifacts Detected</h3>
@@ -1218,7 +1268,7 @@ const DelphiTaskGenerator: React.FC<{
12181268
};
12191269

12201270
return (
1221-
<div className="p-3 mb-4 bg-indigo-50 dark:bg-gray-900/50 rounded-lg border border-indigo-200 dark:border-gray-700">
1271+
<div className="p-3 mb-3 bg-indigo-50 dark:bg-gray-900/50 rounded-lg border border-indigo-200 dark:border-gray-700">
12221272
<div className="flex items-center gap-2 mb-2">
12231273
<BeakerIcon className="h-5 w-5 text-indigo-500"/>
12241274
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Delphi Project Detected</h3>
@@ -1279,7 +1329,7 @@ const LazarusTaskGenerator: React.FC<{
12791329
};
12801330

12811331
return (
1282-
<div className="p-3 mb-4 bg-teal-50 dark:bg-gray-900/50 rounded-lg border border-teal-200 dark:border-gray-700">
1332+
<div className="p-3 mb-3 bg-teal-50 dark:bg-gray-900/50 rounded-lg border border-teal-200 dark:border-gray-700">
12831333
<div className="flex items-center gap-2 mb-2">
12841334
<BeakerIcon className="h-5 w-5 text-teal-500"/>
12851335
<h3 className="text-md font-semibold text-gray-800 dark:text-gray-200">Lazarus/FPC Project Detected</h3>
@@ -1423,7 +1473,7 @@ const TaskStepsEditor: React.FC<{
14231473
}, [repository?.vcs, projectInfo]);
14241474

14251475
return (
1426-
<div className="space-y-4">
1476+
<div className="space-y-3">
14271477
<div className="flex items-center justify-between gap-4">
14281478
<input
14291479
type="text"
@@ -1441,13 +1491,13 @@ const TaskStepsEditor: React.FC<{
14411491
</div>
14421492
</div>
14431493

1444-
<div className="space-y-3">
1494+
<div className="space-y-2.5">
14451495
<TaskVariablesEditor variables={task.variables} onVariablesChange={handleVariablesChange} />
14461496
<TaskEnvironmentVariablesEditor variables={task.environmentVariables} onVariablesChange={handleEnvironmentVariablesChange} />
14471497
</div>
14481498

14491499
{task.steps.length === 0 && (
1450-
<div className="text-center py-6 px-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-700">
1500+
<div className="text-center py-5 px-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-700">
14511501
<CubeTransparentIcon className="mx-auto h-10 w-10 text-gray-400" />
14521502
<h3 className="mt-2 text-sm font-medium text-gray-800 dark:text-gray-200">This task has no steps.</h3>
14531503
<p className="mt-1 text-xs text-gray-500">Add steps manually to begin.</p>
@@ -1464,7 +1514,7 @@ const TaskStepsEditor: React.FC<{
14641514
<LazarusTaskGenerator lazarusCaps={projectInfo?.lazarus} onAddTask={onAddTask} />
14651515
<DelphiTaskGenerator delphiCaps={projectInfo?.delphi} onAddTask={onAddTask} />
14661516

1467-
<div className="space-y-3">
1517+
<div className="space-y-2">
14681518
{task.steps.map((step, index) => (
14691519
<TaskStepItem
14701520
key={step.id}
@@ -1483,15 +1533,15 @@ const TaskStepsEditor: React.FC<{
14831533
</div>
14841534

14851535
{isAddingStep && (
1486-
<div className="space-y-4">
1536+
<div className="space-y-3">
14871537
{STEP_CATEGORIES.map(category => {
14881538
const relevantSteps = category.types.filter(type => availableSteps.includes(type));
14891539
if (relevantSteps.length === 0) return null;
14901540

14911541
return (
14921542
<div key={category.name}>
14931543
<h4 className="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-2">{category.name}</h4>
1494-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
1544+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2.5">
14951545
{relevantSteps.map(type => {
14961546
const { label, icon: Icon, description } = STEP_DEFINITIONS[type];
14971547
return (

0 commit comments

Comments
 (0)