Skip to content
Prev Previous commit
Next Next commit
improvement: more styling changes
  • Loading branch information
Adam Gough authored and Adam Gough committed May 19, 2025
commit d1e756c798f240846d1ed1c9b4958ac452ad7644
8 changes: 4 additions & 4 deletions apps/sim/app/w/[id]/components/loop-node/loop-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const LoopTool = {
loopType: 'for',
count: 5,
collection: '',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: collection field should have a default value that matches the expected type (array/object) for forEach loops

width: 800,
height: 1000,
width: 500,
height: 300,
extent: 'parent',
// Store loop execution state
executionState: {
Expand All @@ -24,8 +24,8 @@ export const LoopTool = {
}
},
style: {
width: 800,
height: 1000,
width: 500,
height: 300,
},
// Specify that this should be rendered as a ReactFlow group node
isResizable: true,
Expand Down
195 changes: 92 additions & 103 deletions apps/sim/app/w/[id]/components/loop-node/loop-node.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { memo, useMemo } from 'react'
import { memo, useMemo, useRef } from 'react'
import { Handle, NodeProps, Position, useReactFlow } from 'reactflow'
import { Trash2 } from 'lucide-react'
import { StartIcon } from '@/components/icons'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { LoopConfigBadges } from './components/loop-config-badges'


export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => {
const { getNodes } = useReactFlow();
const blockRef = useRef<HTMLDivElement>(null);

// Determine nesting level by counting parents
const nestingLevel = useMemo(() => {
Expand All @@ -25,11 +28,10 @@ export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => {
return level;
}, [id, data?.parentId, getNodes]);

// Generate different border styles based on nesting level
const getBorderStyle = () => {
// Generate different background styles based on nesting level
const getNestedStyles = () => {
// Base styles
const styles = {
border: '1px solid rgba(148, 163, 184, 0.6)',
const styles: Record<string, string> = {
backgroundColor: data?.state === 'valid' ? 'rgba(34,197,94,0.05)' : 'transparent',
};
Comment on lines +103 to +105
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

style: The backgroundColor style is duplicated in both getNestedStyles and the Card className (line 60). Consider consolidating the valid state styling to one location


Expand All @@ -39,102 +41,89 @@ export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => {
const colors = ['#e2e8f0', '#cbd5e1', '#94a3b8', '#64748b', '#475569'];
const colorIndex = (nestingLevel - 1) % colors.length;

styles.border = `2px solid ${colors[colorIndex]}`;
styles.backgroundColor = `${colors[colorIndex]}30`; // Slightly more visible background
}

return styles;
};

const borderStyle = getBorderStyle();
const nestedStyles = getNestedStyles();

return (
<div
className={cn(
'relative group-node group',
data?.state === 'valid' && 'border-[#2FB3FF] bg-[rgba(34,197,94,0.05)]',
)}
style={{
width: data.width || 800,
height: data.height || 1000,
borderRadius: '8px',
position: 'relative',
overflow: 'visible',
...borderStyle,
pointerEvents: 'all',
transition: 'width 0.2s ease-out, height 0.2s ease-out, border-color 0.2s ease-in-out, background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
}}
data-node-id={id}
data-type="loopNode"
data-nesting-level={nestingLevel}
>
{/* Critical drag handle that controls only the loop node movement */}
<div
className="absolute top-0 left-0 right-0 h-10 workflow-drag-handle cursor-move z-10"
style={{ pointerEvents: 'auto' }}
/>

{/* Nesting level indicator */}
{nestingLevel > 0 && (
<div
className="absolute top-2 left-2 px-2 py-0.5 text-xs rounded-md bg-background/80 border border-border shadow-sm z-10"
style={{ pointerEvents: 'none' }}
>
Nested: L{nestingLevel}
</div>
)}

{/* Custom visible resize handle */}
<div
className="absolute bottom-2 right-2 w-8 h-8 flex items-center justify-center z-20 text-muted-foreground cursor-se-resize"
style={{ pointerEvents: 'auto' }}
>
</div>

{/* Child nodes container - Set pointerEvents: none to allow events to reach edges */}
<div
className="p-4 h-[calc(100%-10px)]"
data-dragarea="true"
<div className="relative group">
<Card
ref={blockRef}
className={cn(
' select-none relative cursor-default',
'transition-ring transition-block-bg',
'z-[20]',
data?.state === 'valid' && 'ring-2 ring-[#2FB3FF] bg-[rgba(34,197,94,0.05)]',
nestingLevel > 0 && `border border-[0.5px] ${nestingLevel % 2 === 0 ? 'border-slate-300/60' : 'border-slate-400/60'}`
)}
style={{
width: data.width || 500,
height: data.height || 300,
position: 'relative',
minHeight: '100%',
pointerEvents: 'none',
overflow: 'visible',
...nestedStyles,
pointerEvents: 'all',
}}
data-node-id={id}
data-type="loopNode"
data-nesting-level={nestingLevel}
>
{/* Delete button - now always visible */}
{/* Critical drag handle that controls only the loop node movement */}
<div
className="absolute top-3 right-3 w-7 h-7 flex items-center justify-center rounded-full bg-background/90 hover:bg-red-100 border border-border cursor-pointer z-20 shadow-sm opacity-0 group-hover:opacity-100 transition-opacity duration-200"
onClick={(e) => {
e.stopPropagation();
useWorkflowStore.getState().removeBlock(id);
}}
style={{ pointerEvents: 'auto' }} // Re-enable pointer events for this button
className="absolute top-0 left-0 right-0 h-10 workflow-drag-handle cursor-move z-10"
style={{ pointerEvents: 'auto' }}
/>

{/* Custom visible resize handle */}
<div
className="absolute bottom-2 right-2 w-8 h-8 flex items-center justify-center z-20 text-muted-foreground cursor-se-resize"
style={{ pointerEvents: 'auto' }}
>
<Trash2 size={14} className="text-muted-foreground hover:text-red-500" />
</div>

{/* Loop Start Block - positioned at left middle */}
{/* Child nodes container - Set pointerEvents: none to allow events to reach edges */}
<div
className="absolute top-1/2 left-8 w-12 transform -translate-y-1/2"
style={{ pointerEvents: 'auto' }} // Re-enable pointer events
className="p-4 h-[calc(100%-10px)]"
data-dragarea="true"
style={{
position: 'relative',
minHeight: '100%',
pointerEvents: 'none',
}}
>
{/* Delete button - styled like in action-bar.tsx */}
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation();
useWorkflowStore.getState().removeBlock(id);
}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

style: Directly accessing store.getState() in event handler could miss store updates. Consider using a store hook instead

Suggested change
onClick={(e) => {
e.stopPropagation();
useWorkflowStore.getState().removeBlock(id);
}}
onClick={(e) => {
e.stopPropagation();
const removeBlock = useWorkflowStore(state => state.removeBlock);
removeBlock(id);
}}

className="absolute top-2 right-2 z-20 text-gray-500 hover:text-red-600 opacity-0 group-hover:opacity-100 transition-opacity duration-200"
style={{ pointerEvents: 'auto' }}
>
<Trash2 className="h-4 w-4" />
</Button>

{/* Loop Start Block */}
<div
className="bg-white border border-border rounded-md p-2 h-12 relative hover:bg-slate-50 transition-colors flex items-center justify-center"
data-parent-id={id}
className="absolute top-1/2 left-8 w-10 bg-[#2FB3FF] rounded-md p-2 h-10 flex items-center justify-center transform -translate-y-1/2"
style={{ pointerEvents: 'auto' }}
data-parent-id={id}
data-node-role="loop-start"
data-extent="parent"
>
<div
className="bg-[#2FB3FF] rounded-full p-1.5 flex items-center justify-center"
style={{ zIndex: 1}}>
<StartIcon className="text-white w-6 h-6" />
</div>

<StartIcon className="text-white w-6 h-6" />

<Handle
type="source"
position={Position.Right}
id="loop-start-source"
className="!w-[7px] !h-4 !bg-[#2FB3FF] dark:!bg-[#2FB3FF]! !border-none !z-[30] group-hover:!shadow-[0_0_0_3px_rgba(64,224,208,0.15)] hover:!w-[10px] hover:!right-[-10px] hover:!rounded-r-full !cursor-crosshair transition-[colors] duration-150"
className="!w-[6px] !h-4 !bg-slate-300 dark:!bg-slate-500 !rounded-[2px] !border-none !z-[30] hover:!w-[10px] hover:!right-[-10px] hover:!rounded-r-full hover:!rounded-l-none !cursor-crosshair -[colors] duration-150"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

syntax: There appears to be a typo in the className: '-[colors]' should likely be 'transition-[colors]'

Suggested change
className="!w-[6px] !h-4 !bg-slate-300 dark:!bg-slate-500 !rounded-[2px] !border-none !z-[30] hover:!w-[10px] hover:!right-[-10px] hover:!rounded-r-full hover:!rounded-l-none !cursor-crosshair -[colors] duration-150"
className="!w-[6px] !h-4 !bg-slate-300 dark:!bg-slate-500 !rounded-[2px] !border-none !z-[30] hover:!w-[10px] hover:!right-[-10px] hover:!rounded-r-full hover:!rounded-l-none !cursor-crosshair transition-[colors] duration-150"

style={{
right: "-6px",
top: "50%",
Expand All @@ -145,37 +134,37 @@ export const LoopNodeComponent = memo(({ data, selected, id }: NodeProps) => {
/>
</div>
</div>
</div>

{/* Input handle on left middle */}
<Handle
type="target"
position={Position.Left}
className="!w-[10px] !h-5 !bg-slate-300 dark:!bg-slate-500 !rounded-[2px] !border-none !z-[30] group-hover:!shadow-[0_0_0_3px_rgba(156,163,175,0.15)] hover:!w-[10px] hover:!left-[-10px] hover:!rounded-l-full hover:!rounded-r-none !cursor-crosshair transition-[colors] duration-150"
style={{
left: "-6px",
top: "50%",
transform: "translateY(-50%)",
pointerEvents: 'auto'
}}
/>
{/* Input handle on left middle */}
<Handle
type="target"
position={Position.Left}
className="!w-[7px] !h-5 !bg-slate-300 dark:!bg-slate-500 !rounded-[2px] !border-none !z-[30] hover:!w-[10px] hover:!left-[-10px] hover:!rounded-l-full hover:!rounded-r-none !cursor-crosshair transition-[colors] duration-150"
style={{
left: "-7px",
top: "50%",
transform: "translateY(-50%)",
pointerEvents: 'auto'
}}
/>

{/* Output handle on right middle */}
<Handle
type="source"
position={Position.Right}
className="!w-[10px] !h-5 !bg-slate-300 dark:!bg-slate-500 !rounded-[2px] !border-none !z-[30] group-hover:!shadow-[0_0_0_3px_rgba(156,163,175,0.15)] hover:!w-[10px] hover:!right-[-10px] hover:!rounded-r-full hover:!rounded-l-none !cursor-crosshair transition-[colors] duration-150"
style={{
right: "-6px",
top: "50%",
transform: "translateY(-50%)",
pointerEvents: 'auto'
}}
id="loop-end-source"
/>
{/* Output handle on right middle */}
<Handle
type="source"
position={Position.Right}
className="!w-[7px] !h-5 !bg-slate-300 dark:!bg-slate-500 !rounded-[2px] !border-none !z-[30] hover:!w-[10px] hover:!right-[-10px] hover:!rounded-r-full hover:!rounded-l-none !cursor-crosshair transition-[colors] duration-150"
style={{
right: "-7px",
top: "50%",
transform: "translateY(-50%)",
pointerEvents: 'auto'
}}
id="loop-end-source"
/>

{/* Loop Configuration Badges */}
<LoopConfigBadges nodeId={id} data={data} />
{/* Loop Configuration Badges */}
<LoopConfigBadges nodeId={id} data={data} />
</Card>
</div>
)
})
Expand Down
Loading