Skip to content

Commit fc3419e

Browse files
committed
feat(web): introduce ActionBarButton component for workflow-canvas
1 parent fcc2882 commit fc3419e

1 file changed

Lines changed: 126 additions & 64 deletions

File tree

apps/web/src/components/workflow/workflow-canvas.tsx

Lines changed: 126 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import {
1818
} from "@xyflow/react";
1919
import {
2020
ArrowUpToLine,
21-
ChevronDown,
22-
ChevronUp,
21+
Eye,
22+
EyeOff,
2323
PanelLeft,
2424
PanelLeftClose,
2525
Play,
@@ -98,6 +98,53 @@ function CanvasButton({
9898
);
9999
}
100100

101+
interface ActionBarButtonProps {
102+
onClick: (e: React.MouseEvent) => void;
103+
disabled?: boolean;
104+
className?: string;
105+
tooltip: string;
106+
children: React.ReactNode;
107+
position?: "first" | "middle" | "last" | "only";
108+
}
109+
110+
function ActionBarButton({
111+
onClick,
112+
disabled = false,
113+
className = "",
114+
tooltip,
115+
children,
116+
position = "middle",
117+
}: ActionBarButtonProps) {
118+
const roundingClass = {
119+
first: "rounded-l-lg rounded-r-none",
120+
middle: "rounded-none border-l-0",
121+
last: "rounded-r-lg rounded-l-none border-l-0",
122+
only: "rounded-lg",
123+
}[position];
124+
125+
return (
126+
<Tooltip>
127+
<TooltipTrigger asChild>
128+
<Button
129+
onClick={onClick}
130+
disabled={disabled}
131+
className={cn(
132+
"h-10 px-3 border-neutral-300 shadow-sm",
133+
roundingClass,
134+
className,
135+
{ "opacity-50 cursor-not-allowed": disabled }
136+
)}
137+
>
138+
{children}
139+
</Button>
140+
</TooltipTrigger>
141+
<TooltipContent>
142+
<p>{tooltip}</p>
143+
</TooltipContent>
144+
</Tooltip>
145+
);
146+
}
147+
101148
export interface WorkflowCanvasProps {
102149
nodes: ReactFlowNode<WorkflowNodeType>[];
103150
edges: ReactFlowEdge<WorkflowEdgeType>[];
@@ -148,55 +195,57 @@ function ActionButton({
148195
}: ActionButtonProps) {
149196
const statusConfig = {
150197
idle: {
151-
icon: <Play className="!size-5" />,
198+
icon: <Play className="!size-4" />,
152199
title: "Execute Workflow",
153-
className: "bg-green-600 hover:bg-green-700 text-white",
200+
className: "bg-green-600 hover:bg-green-700 text-white border-green-600",
154201
},
155202
submitted: {
156-
icon: <Square className="!size-5" />,
203+
icon: <Square className="!size-4" />,
157204
title: "Stop Execution",
158-
className: "bg-red-500 hover:bg-red-600 text-white",
205+
className: "bg-red-500 hover:bg-red-600 text-white border-red-500",
159206
},
160207
executing: {
161-
icon: <Square className="!size-5" />,
208+
icon: <Square className="!size-4" />,
162209
title: "Stop Execution",
163-
className: "bg-red-500 hover:bg-red-600 text-white",
210+
className: "bg-red-500 hover:bg-red-600 text-white border-red-500",
164211
},
165212
completed: {
166-
icon: <X className="!size-5" />,
213+
icon: <X className="!size-4" />,
167214
title: "Clear Outputs & Reset",
168-
className: "bg-emerald-600 hover:bg-emerald-700 text-white",
215+
className:
216+
"bg-emerald-600 hover:bg-emerald-700 text-white border-emerald-600",
169217
},
170218
error: {
171-
icon: <X className="!size-5" />,
219+
icon: <X className="!size-4" />,
172220
title: "Clear Errors & Reset",
173-
className: "bg-rose-600 hover:bg-rose-700 text-white",
221+
className: "bg-rose-600 hover:bg-rose-700 text-white border-rose-600",
174222
},
175223
cancelled: {
176-
icon: <Play className="!size-5" />,
224+
icon: <Play className="!size-4" />,
177225
title: "Restart Workflow",
178-
className: "bg-neutral-600 hover:bg-neutral-700 text-white",
226+
className:
227+
"bg-neutral-600 hover:bg-neutral-700 text-white border-neutral-600",
179228
},
180229
paused: {
181-
icon: <Play className="!size-5" />,
230+
icon: <Play className="!size-4" />,
182231
title: "Resume Workflow",
183-
className: "bg-blue-500 hover:bg-blue-600 text-white",
232+
className: "bg-blue-500 hover:bg-blue-600 text-white border-blue-500",
184233
},
185234
};
186235

187236
// Use a default config if the status isn't in our mapping
188237
const config = statusConfig[workflowStatus] || statusConfig.idle;
189238

190239
return (
191-
<CanvasButton
240+
<ActionBarButton
192241
onClick={onClick}
193242
disabled={disabled}
194243
className={config.className}
195-
position="right-28"
196244
tooltip={config.title}
245+
position="first"
197246
>
198247
{config.icon}
199-
</CanvasButton>
248+
</ActionBarButton>
200249
);
201250
}
202251

@@ -208,15 +257,15 @@ function DeployButton({
208257
disabled?: boolean;
209258
}) {
210259
return (
211-
<CanvasButton
260+
<ActionBarButton
212261
onClick={onClick}
213262
disabled={disabled}
214-
className="bg-blue-600 hover:bg-blue-700 text-white"
215-
position="right-16"
263+
className="bg-blue-600 hover:bg-blue-700 text-white border-blue-600"
216264
tooltip="Deploy Workflow"
265+
position="middle"
217266
>
218-
<ArrowUpToLine className="!size-5" />
219-
</CanvasButton>
267+
<ArrowUpToLine className="!size-4" />
268+
</ActionBarButton>
220269
);
221270
}
222271

@@ -227,17 +276,18 @@ type SidebarToggleProps = {
227276

228277
function SidebarToggle({ onClick, isSidebarVisible }: SidebarToggleProps) {
229278
return (
230-
<CanvasButton
279+
<ActionBarButton
231280
onClick={onClick}
232-
position="right-4"
233281
tooltip={isSidebarVisible ? "Hide Sidebar" : "Show Sidebar"}
282+
className="bg-neutral-100 hover:bg-neutral-200 text-neutral-700 border-neutral-300"
283+
position="last"
234284
>
235285
{isSidebarVisible ? (
236-
<PanelLeftClose className="!size-5 rotate-180" />
286+
<PanelLeftClose className="!size-4 rotate-180" />
237287
) : (
238-
<PanelLeft className="!size-5 rotate-180" />
288+
<PanelLeft className="!size-4 rotate-180" />
239289
)}
240-
</CanvasButton>
290+
</ActionBarButton>
241291
);
242292
}
243293

@@ -251,25 +301,25 @@ function OutputsToggle({
251301
disabled?: boolean;
252302
}) {
253303
return (
254-
<CanvasButton
304+
<ActionBarButton
255305
onClick={onClick}
256306
disabled={disabled}
257-
className="bg-neutral-600 hover:bg-neutral-700 text-white"
258-
position="right-40"
307+
className="bg-neutral-600 hover:bg-neutral-700 text-white border-neutral-600"
259308
tooltip={
260309
disabled
261310
? "No outputs to show"
262311
: expandedOutputs
263-
? "Collapse All Outputs"
264-
: "Expand All Outputs"
312+
? "Hide All Outputs"
313+
: "Show All Outputs"
265314
}
315+
position="middle"
266316
>
267317
{expandedOutputs ? (
268-
<ChevronUp className="!size-5" />
318+
<EyeOff className="!size-4" />
269319
) : (
270-
<ChevronDown className="!size-5" />
320+
<Eye className="!size-4" />
271321
)}
272-
</CanvasButton>
322+
</ActionBarButton>
273323
);
274324
}
275325

@@ -353,6 +403,45 @@ export function WorkflowCanvas({
353403
</div>
354404
)}
355405

406+
{/* Main Action Bar */}
407+
{!readonly &&
408+
(onAction ||
409+
onDeploy ||
410+
onToggleExpandedOutputs ||
411+
onToggleSidebar) && (
412+
<div className="absolute top-4 right-4 flex items-center rounded-lg shadow-lg bg-background z-50 overflow-hidden">
413+
{onAction && (
414+
<ActionButton
415+
onClick={onAction}
416+
workflowStatus={workflowStatus}
417+
disabled={nodes.length === 0}
418+
/>
419+
)}
420+
421+
{onToggleExpandedOutputs && (
422+
<OutputsToggle
423+
onClick={onToggleExpandedOutputs}
424+
expandedOutputs={expandedOutputs}
425+
disabled={!hasAnyOutputs}
426+
/>
427+
)}
428+
429+
{onDeploy && (
430+
<DeployButton
431+
onClick={onDeploy}
432+
disabled={nodes.length === 0}
433+
/>
434+
)}
435+
436+
{onToggleSidebar && isSidebarVisible !== undefined && (
437+
<SidebarToggle
438+
onClick={onToggleSidebar}
439+
isSidebarVisible={isSidebarVisible}
440+
/>
441+
)}
442+
</div>
443+
)}
444+
356445
{onAddNode && !readonly && (
357446
<CanvasButton
358447
onClick={(e) => {
@@ -366,33 +455,6 @@ export function WorkflowCanvas({
366455
<Plus className="!size-5" />
367456
</CanvasButton>
368457
)}
369-
370-
{onToggleExpandedOutputs && (
371-
<OutputsToggle
372-
onClick={onToggleExpandedOutputs}
373-
expandedOutputs={expandedOutputs}
374-
disabled={!hasAnyOutputs}
375-
/>
376-
)}
377-
378-
{onAction && !readonly && (
379-
<ActionButton
380-
onClick={onAction}
381-
workflowStatus={workflowStatus}
382-
disabled={nodes.length === 0}
383-
/>
384-
)}
385-
386-
{onDeploy && !readonly && (
387-
<DeployButton onClick={onDeploy} disabled={nodes.length === 0} />
388-
)}
389-
390-
{onToggleSidebar && isSidebarVisible !== undefined && (
391-
<SidebarToggle
392-
onClick={onToggleSidebar}
393-
isSidebarVisible={isSidebarVisible}
394-
/>
395-
)}
396458
</ReactFlow>
397459
</TooltipProvider>
398460
);

0 commit comments

Comments
 (0)