@@ -27,6 +27,7 @@ import {
2727 Square ,
2828 X ,
2929} from "lucide-react" ;
30+ import { useEffect , useRef } from "react" ;
3031
3132import { Button } from "@/components/ui/button" ;
3233import {
@@ -76,7 +77,7 @@ function CanvasButton({
7677 : `absolute top-4 ${ position } z-50` ;
7778
7879 return (
79- < Tooltip >
80+ < Tooltip delayDuration = { 0 } >
8081 < TooltipTrigger asChild >
8182 < Button
8283 onClick = { onClick }
@@ -102,7 +103,7 @@ interface ActionBarButtonProps {
102103 onClick : ( e : React . MouseEvent ) => void ;
103104 disabled ?: boolean ;
104105 className ?: string ;
105- tooltip : string ;
106+ tooltip : React . ReactNode ;
106107 children : React . ReactNode ;
107108 position ?: "first" | "middle" | "last" | "only" ;
108109}
@@ -117,30 +118,27 @@ function ActionBarButton({
117118} : ActionBarButtonProps ) {
118119 const roundingClass = {
119120 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 " ,
121+ middle : "rounded-none" ,
122+ last : "rounded-r-lg rounded-l-none" ,
122123 only : "rounded-lg" ,
123124 } [ position ] ;
124125
125126 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 >
127+ < Tooltip delayDuration = { 0 } >
128+ < div className = { cn ( "bg-background" , roundingClass ) } >
129+ < TooltipTrigger asChild >
130+ < Button
131+ onClick = { onClick }
132+ disabled = { disabled }
133+ className = { cn ( "h-10 px-3" , className , roundingClass , {
134+ "opacity-50 cursor-not-allowed" : disabled ,
135+ } ) }
136+ >
137+ { children }
138+ </ Button >
139+ </ TooltipTrigger >
140+ < TooltipContent > { tooltip } </ TooltipContent >
141+ </ div >
144142 </ Tooltip >
145143 ) ;
146144}
@@ -197,38 +195,45 @@ function ActionButton({
197195 idle : {
198196 icon : < Play className = "!size-4" /> ,
199197 title : "Execute Workflow" ,
198+ shortcut : "⌘⏎" ,
200199 className : "bg-green-600 hover:bg-green-700 text-white border-green-600" ,
201200 } ,
202201 submitted : {
203202 icon : < Square className = "!size-4" /> ,
204203 title : "Stop Execution" ,
204+ shortcut : "⌘⏎" ,
205205 className : "bg-red-500 hover:bg-red-600 text-white border-red-500" ,
206206 } ,
207207 executing : {
208208 icon : < Square className = "!size-4" /> ,
209209 title : "Stop Execution" ,
210+ shortcut : "⌘⏎" ,
210211 className : "bg-red-500 hover:bg-red-600 text-white border-red-500" ,
211212 } ,
212213 completed : {
213214 icon : < X className = "!size-4" /> ,
214215 title : "Clear Outputs & Reset" ,
216+ shortcut : "⌘⏎" ,
215217 className :
216218 "bg-emerald-600 hover:bg-emerald-700 text-white border-emerald-600" ,
217219 } ,
218220 error : {
219221 icon : < X className = "!size-4" /> ,
220222 title : "Clear Errors & Reset" ,
223+ shortcut : "⌘⏎" ,
221224 className : "bg-rose-600 hover:bg-rose-700 text-white border-rose-600" ,
222225 } ,
223226 cancelled : {
224227 icon : < Play className = "!size-4" /> ,
225228 title : "Restart Workflow" ,
229+ shortcut : "⌘⏎" ,
226230 className :
227231 "bg-neutral-600 hover:bg-neutral-700 text-white border-neutral-600" ,
228232 } ,
229233 paused : {
230234 icon : < Play className = "!size-4" /> ,
231235 title : "Resume Workflow" ,
236+ shortcut : "⌘⏎" ,
232237 className : "bg-blue-500 hover:bg-blue-600 text-white border-blue-500" ,
233238 } ,
234239 } ;
@@ -241,7 +246,18 @@ function ActionButton({
241246 onClick = { onClick }
242247 disabled = { disabled }
243248 className = { config . className }
244- tooltip = { config . title }
249+ tooltip = {
250+ < div className = "flex items-center gap-2" >
251+ < span > { config . title } </ span >
252+ < div className = "flex items-center gap-1" >
253+ { config . shortcut . split ( "" ) . map ( ( key ) => (
254+ < kbd className = "px-1 py-0.25 text-xs rounded border font-mono" >
255+ { key }
256+ </ kbd >
257+ ) ) }
258+ </ div >
259+ </ div >
260+ }
245261 position = "first"
246262 >
247263 { config . icon }
@@ -261,8 +277,8 @@ function DeployButton({
261277 onClick = { onClick }
262278 disabled = { disabled }
263279 className = "bg-blue-600 hover:bg-blue-700 text-white border-blue-600"
264- tooltip = " Deploy Workflow"
265- position = "middle "
280+ tooltip = { < p > Deploy Workflow</ p > }
281+ position = "last "
266282 >
267283 < ArrowUpToLine className = "!size-4" />
268284 </ ActionBarButton >
@@ -278,9 +294,8 @@ function SidebarToggle({ onClick, isSidebarVisible }: SidebarToggleProps) {
278294 return (
279295 < ActionBarButton
280296 onClick = { onClick }
281- tooltip = { isSidebarVisible ? "Hide Sidebar" : "Show Sidebar" }
282- className = "bg-neutral-100 hover:bg-neutral-200 text-neutral-700 border-neutral-300"
283- position = "last"
297+ tooltip = { < p > { isSidebarVisible ? "Hide Sidebar" : "Show Sidebar" } </ p > }
298+ position = "only"
284299 >
285300 { isSidebarVisible ? (
286301 < PanelLeftClose className = "!size-4 rotate-180" />
@@ -300,18 +315,18 @@ function OutputsToggle({
300315 expandedOutputs : boolean ;
301316 disabled ?: boolean ;
302317} ) {
318+ const tooltipText = disabled
319+ ? "No outputs to show"
320+ : expandedOutputs
321+ ? "Hide All Outputs"
322+ : "Show All Outputs" ;
323+
303324 return (
304325 < ActionBarButton
305326 onClick = { onClick }
306327 disabled = { disabled }
307328 className = "bg-neutral-600 hover:bg-neutral-700 text-white border-neutral-600"
308- tooltip = {
309- disabled
310- ? "No outputs to show"
311- : expandedOutputs
312- ? "Hide All Outputs"
313- : "Show All Outputs"
314- }
329+ tooltip = { < p > { tooltipText } </ p > }
315330 position = "middle"
316331 >
317332 { expandedOutputs ? (
@@ -352,6 +367,51 @@ export function WorkflowCanvas({
352367 node . data . outputs . some ( ( output ) => output . value !== undefined )
353368 ) ;
354369
370+ const reactFlowRef = useRef < ReactFlowInstance <
371+ ReactFlowNode < WorkflowNodeType > ,
372+ ReactFlowEdge < WorkflowEdgeType >
373+ > | null > ( null ) ;
374+
375+ // Keyboard shortcut handling
376+ useEffect ( ( ) => {
377+ const handleKeyDown = ( event : KeyboardEvent ) => {
378+ // Cmd+Enter (Mac) or Ctrl+Enter (Windows/Linux) - Execute workflow
379+ if ( ( event . metaKey || event . ctrlKey ) && event . key === "Enter" ) {
380+ event . preventDefault ( ) ;
381+ if ( onAction && ! readonly && nodes . length > 0 ) {
382+ const syntheticEvent = new MouseEvent ( "click" , {
383+ bubbles : true ,
384+ cancelable : true ,
385+ } ) as unknown as React . MouseEvent ;
386+ onAction ( syntheticEvent ) ;
387+ }
388+ return ;
389+ }
390+
391+ // Esc - Center the chart
392+ if ( event . key === "Escape" ) {
393+ event . preventDefault ( ) ;
394+ if ( reactFlowRef . current ) {
395+ reactFlowRef . current . fitView ( { padding : 0.1 , duration : 300 } ) ;
396+ }
397+ return ;
398+ }
399+ } ;
400+
401+ document . addEventListener ( "keydown" , handleKeyDown ) ;
402+ return ( ) => document . removeEventListener ( "keydown" , handleKeyDown ) ;
403+ } , [ onAction , readonly , nodes . length ] ) ;
404+
405+ const handleInit = (
406+ instance : ReactFlowInstance <
407+ ReactFlowNode < WorkflowNodeType > ,
408+ ReactFlowEdge < WorkflowEdgeType >
409+ >
410+ ) => {
411+ reactFlowRef . current = instance ;
412+ onInit ( instance ) ;
413+ } ;
414+
355415 return (
356416 < TooltipProvider >
357417 < ReactFlow
@@ -370,7 +430,7 @@ export function WorkflowCanvas({
370430 connectionMode = { ConnectionMode . Strict }
371431 connectionLineComponent = { WorkflowConnectionLine }
372432 connectionRadius = { 8 }
373- onInit = { onInit }
433+ onInit = { handleInit }
374434 isValidConnection = { isValidConnection }
375435 fitView
376436 minZoom = { 0.05 }
@@ -404,45 +464,50 @@ export function WorkflowCanvas({
404464 ) }
405465
406466 { /* Main Action Bar */ }
407- { ( ( ! readonly && ( onAction || onDeploy || onToggleExpandedOutputs ) ) ||
408- onToggleSidebar ) && (
409- < div className = "absolute top-4 right-4 flex items-center rounded-lg shadow-lg bg-background z-50 overflow-hidden" >
410- { ! readonly && onAction && (
467+ { ! readonly && ( onAction || onDeploy || onToggleExpandedOutputs ) && (
468+ < div
469+ className = { cn (
470+ "absolute top-4 flex items-center gap-0.5 z-50" ,
471+ onToggleSidebar && isSidebarVisible !== undefined
472+ ? "right-16" // Position to the left of sidebar with spacing
473+ : "right-4" // Position at right edge if no sidebar
474+ ) }
475+ >
476+ { onAction && (
411477 < ActionButton
412478 onClick = { onAction }
413479 workflowStatus = { workflowStatus }
414480 disabled = { nodes . length === 0 }
415481 />
416482 ) }
417483
418- { ! readonly && onToggleExpandedOutputs && (
484+ { onToggleExpandedOutputs && (
419485 < OutputsToggle
420486 onClick = { onToggleExpandedOutputs }
421487 expandedOutputs = { expandedOutputs }
422488 disabled = { ! hasAnyOutputs }
423489 />
424490 ) }
425491
426- { ! readonly && onDeploy && (
492+ { onDeploy && (
427493 < DeployButton onClick = { onDeploy } disabled = { nodes . length === 0 } />
428494 ) }
495+ </ div >
496+ ) }
429497
430- { onToggleSidebar && isSidebarVisible !== undefined && (
431- < SidebarToggle
432- onClick = { onToggleSidebar }
433- isSidebarVisible = { isSidebarVisible }
434- />
435- ) }
498+ { /* Sidebar Toggle - Rightmost position */ }
499+ { onToggleSidebar && isSidebarVisible !== undefined && (
500+ < div className = "absolute top-4 right-4 z-50" >
501+ < SidebarToggle
502+ onClick = { onToggleSidebar }
503+ isSidebarVisible = { isSidebarVisible }
504+ />
436505 </ div >
437506 ) }
438507
439508 { onAddNode && ! readonly && (
440509 < CanvasButton
441- onClick = { ( e ) => {
442- e . preventDefault ( ) ;
443- e . stopPropagation ( ) ;
444- onAddNode ( ) ;
445- } }
510+ onClick = { onAddNode }
446511 position = "bottom-4 right-4"
447512 tooltip = "Add Node"
448513 >
0 commit comments