Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 66 additions & 15 deletions src/components/create-job/plugins/PluginsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { POLICY_TYPES } from '@data/policyTypes';
import { InteractionContextType, useInteractionContext } from '@lib/contexts/interaction';
import { generatePluginName, getPluginName } from '@lib/pluginNames';
import { SlateCard } from '@shared/cards/SlateCard';
import Expander from '@shared/Expander';
import DeeployErrorAlert from '@shared/jobs/DeeployErrorAlert';
import AddJobCard from '@shared/projects/AddJobCard';
import { SmallTag } from '@shared/SmallTag';
import { computeDependencyTree } from '@lib/dependencyTree';
import { BasePluginType, GenericPlugin, Plugin, PluginType } from '@typedefs/steps/deploymentStepTypes';
import { useEffect, useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
import toast from 'react-hot-toast';
import { RiBox3Line, RiDeleteBin2Line, RiTerminalBoxLine } from 'react-icons/ri';
Expand Down Expand Up @@ -81,6 +82,7 @@ export default function PluginsSection() {
});

const plugins = fields as PluginWithId[];
const [expandedPlugins, setExpandedPlugins] = useState<Record<string, boolean>>({});

const previousPluginsLengthRef = useRef<number>(plugins.length);

Expand Down Expand Up @@ -112,7 +114,20 @@ export default function PluginsSection() {

// Clean stale shmem references when a plugin is removed
const handleRemovePlugin = (indexToRemove: number) => {
const removedPluginId = plugins[indexToRemove]?.id;
const removedName = watchedPlugins[indexToRemove]?.pluginName;

if (removedPluginId) {
setExpandedPlugins((previous) => {
if (!(removedPluginId in previous)) {
return previous;
}

const { [removedPluginId]: _removed, ...next } = previous;
return next;
});
}

remove(indexToRemove);

if (!removedName) return;
Expand Down Expand Up @@ -252,6 +267,20 @@ export default function PluginsSection() {
previousPluginsLengthRef.current = plugins.length;
}, [plugins]);

useEffect(() => {
const pluginIds = new Set(plugins.map((plugin) => plugin.id));

setExpandedPlugins((previous) => {
const next = Object.fromEntries(Object.entries(previous).filter(([id]) => pluginIds.has(id)));

if (Object.keys(next).length === Object.keys(previous).length) {
return previous;
}

return next;
});
}, [plugins]);

return (
<div className="col gap-6">
{fields.length < 5 && (
Expand All @@ -265,11 +294,25 @@ export default function PluginsSection() {
<div className="col gap-6">
{plugins.map((plugin, index) => {
const { title, element } = getPluginAlias(plugin, index);
const isExpanded = expandedPlugins[plugin.id] ?? true;

return (
<div key={plugin.id} id={`plugin-card-${plugin.id}`}>
<SlateCard
titleElement={element}
titleElement={
<div className="row gap-2">
<Expander
expanded={isExpanded}
onToggle={() =>
setExpandedPlugins((previous) => ({
...previous,
[plugin.id]: !(previous[plugin.id] ?? true),
}))
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new expand/collapse control is rendered via Expander, which currently uses a clickable <div> (no keyboard interaction, no role="button", no aria-expanded/label). Since this PR introduces the expander in the job creation flow, please make the control accessible (prefer a <button type="button"> in Expander with aria-expanded and an aria-label, or add equivalent keyboard/ARIA support).

Suggested change
}
}
aria-label={
isExpanded
? 'Collapse plugin details'
: 'Expand plugin details'
}
aria-expanded={isExpanded}
aria-controls={`plugin-card-${plugin.id}`}

Copilot uses AI. Check for mistakes.
/>
{element}
</div>
}
label={
<div
className="compact cursor-pointer text-red-600 hover:opacity-50"
Expand Down Expand Up @@ -300,19 +343,27 @@ export default function PluginsSection() {
</div>
}
>
<>
{plugin.basePluginType === BasePluginType.Generic ? (
<>
{(plugin as GenericPlugin).deploymentType.pluginType === PluginType.Container ? (
<CARInputsSection name={`${name}.${index}`} availablePlugins={availablePluginsByIndex[index]} />
) : (
<WARInputsSection name={`${name}.${index}`} availablePlugins={availablePluginsByIndex[index]} />
)}
</>
) : (
<NativeInputsSection name={`${name}.${index}`} />
)}
</>
{isExpanded ? (
<>
{plugin.basePluginType === BasePluginType.Generic ? (
<>
{(plugin as GenericPlugin).deploymentType.pluginType === PluginType.Container ? (
<CARInputsSection
name={`${name}.${index}`}
availablePlugins={availablePluginsByIndex[index]}
/>
) : (
<WARInputsSection
name={`${name}.${index}`}
availablePlugins={availablePluginsByIndex[index]}
/>
)}
</>
) : (
<NativeInputsSection name={`${name}.${index}`} />
)}
</>
) : null}
</SlateCard>
</div>
);
Expand Down
6 changes: 4 additions & 2 deletions src/shared/cards/SlateCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FunctionComponent, PropsWithChildren } from 'react';
import { Children, FunctionComponent, PropsWithChildren } from 'react';

interface Props {
title?: string;
Expand All @@ -7,6 +7,8 @@ interface Props {
}

export const SlateCard: FunctionComponent<PropsWithChildren<Props>> = ({ children, title, titleElement, label }) => {
const hasChildren = Children.count(children) > 0;

return (
<div className="col justify-center gap-4 rounded-lg bg-slate-100 px-4 py-5">
{(!!title || !!titleElement || !!label) && (
Expand All @@ -17,7 +19,7 @@ export const SlateCard: FunctionComponent<PropsWithChildren<Props>> = ({ childre
</div>
)}

<div className="col gap-4">{children}</div>
{hasChildren && <div className="col gap-4">{children}</div>}
</div>
);
};
Loading