11/**
22 * DataStoreNode — Key-value data store operations in workflows.
3- * Cyan/teal color theme.
3+ * Database/storage look with cyan gradient header, color-coded operation badges,
4+ * monospace key display, and dimmed namespace prefix.
45 */
56
67import { memo } from 'react' ;
@@ -26,6 +27,15 @@ export interface DataStoreNodeData extends Record<string, unknown> {
2627
2728export type DataStoreNodeType = Node < DataStoreNodeData > ;
2829
30+ /** Per-operation badge colors */
31+ const operationStyles : Record < string , { bg : string ; text : string } > = {
32+ get : { bg : 'bg-emerald-100 dark:bg-emerald-900/40' , text : 'text-emerald-700 dark:text-emerald-300' } ,
33+ set : { bg : 'bg-blue-100 dark:bg-blue-900/40' , text : 'text-blue-700 dark:text-blue-300' } ,
34+ delete : { bg : 'bg-red-100 dark:bg-red-900/40' , text : 'text-red-700 dark:text-red-300' } ,
35+ list : { bg : 'bg-violet-100 dark:bg-violet-900/40' , text : 'text-violet-700 dark:text-violet-300' } ,
36+ has : { bg : 'bg-amber-100 dark:bg-amber-900/40' , text : 'text-amber-700 dark:text-amber-300' } ,
37+ } ;
38+
2939const statusStyles : Record < NodeExecutionStatus , { border : string ; bg : string } > = {
3040 pending : { border : 'border-cyan-300 dark:border-cyan-700' , bg : '' } ,
3141 running : { border : 'border-warning' , bg : 'bg-warning/5' } ,
@@ -45,82 +55,104 @@ function DataStoreNodeComponent({ data, selected }: NodeProps<DataStoreNodeType>
4555 const status = ( data . executionStatus as NodeExecutionStatus | undefined ) ?? 'pending' ;
4656 const style = statusStyles [ status ] ;
4757 const StatusIcon = statusIcons [ status ] ;
58+ const operation = ( data . operation as string ) ?? '' ;
59+ const opStyle = operationStyles [ operation ] ?? {
60+ bg : 'bg-cyan-100 dark:bg-cyan-900/40' ,
61+ text : 'text-cyan-700 dark:text-cyan-300' ,
62+ } ;
4863
4964 return (
5065 < div
5166 className = { `
52- relative min-w-[180px ] max-w-[260px ] rounded-lg border-2 shadow-sm
53- bg-cyan-50 dark:bg-cyan-950/30
67+ relative min-w-[200px ] max-w-[280px ] rounded-lg border-2 shadow-md overflow-hidden
68+ bg-white dark:bg-gray-900
5469 ${ style . border } ${ style . bg }
5570 ${ selected ? 'ring-2 ring-cyan-500 ring-offset-1' : '' }
5671 ${ status === 'running' ? 'animate-pulse' : '' }
5772 transition-all duration-200
5873 ` }
5974 >
75+ { /* Input Handle */ }
6076 < Handle
6177 type = "target"
6278 position = { Position . Top }
6379 className = "!w-3 !h-3 !bg-cyan-500 !border-2 !border-white dark:!border-cyan-950"
6480 />
6581
66- < div className = "px-3 py-2.5" >
82+ { /* Gradient Header Bar */ }
83+ < div className = "bg-gradient-to-r from-cyan-500 to-sky-500 px-3 py-2 flex items-center gap-2" >
84+ < div className = "w-6 h-6 rounded-full bg-white/20 flex items-center justify-center shrink-0" >
85+ < Database className = "w-3.5 h-3.5 text-white" />
86+ </ div >
87+ < span className = "font-semibold text-sm text-white truncate flex-1" >
88+ { ( data . label as string ) || 'Data Store' }
89+ </ span >
90+ { StatusIcon && (
91+ < StatusIcon
92+ className = { `w-4 h-4 shrink-0 ${
93+ status === 'success'
94+ ? 'text-emerald-200'
95+ : status === 'error'
96+ ? 'text-red-200'
97+ : status === 'running'
98+ ? 'text-amber-200'
99+ : 'text-white/60'
100+ } `}
101+ />
102+ ) }
103+ </ div >
104+
105+ { /* Body Content */ }
106+ < div className = "px-3 py-2 space-y-1.5" >
107+ { /* Cylinder visual + operation badge */ }
67108 < div className = "flex items-center gap-2" >
68- < div className = "w-6 h-6 rounded-full bg-cyan-500/20 flex items-center justify-center shrink-0" >
69- < Database className = "w-3.5 h-3.5 text-cyan-600 dark:text-cyan-400" />
70- </ div >
71- < span className = "font-medium text-sm text-cyan-900 dark:text-cyan-100 truncate flex-1" >
72- { ( data . label as string ) || 'Data Store' }
73- </ span >
74- { StatusIcon && (
75- < StatusIcon
76- className = { `w-4 h-4 shrink-0 ${
77- status === 'success'
78- ? 'text-success'
79- : status === 'error'
80- ? 'text-error'
81- : status === 'running'
82- ? 'text-warning'
83- : 'text-text-muted'
84- } `}
85- />
109+ { /* Mini cylinder icon */ }
110+ < svg className = "w-5 h-6 text-cyan-300 dark:text-cyan-700 shrink-0" viewBox = "0 0 20 24" fill = "none" stroke = "currentColor" strokeWidth = "1.5" >
111+ < ellipse cx = "10" cy = "5" rx = "8" ry = "3" />
112+ < path d = "M2 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5" />
113+ < ellipse cx = "10" cy = "12" rx = "8" ry = "2" strokeDasharray = "3 2" opacity = "0.4" />
114+ </ svg >
115+ { operation && (
116+ < span className = { `px-2 py-0.5 text-[10px] font-extrabold rounded uppercase tracking-wider ${ opStyle . bg } ${ opStyle . text } ` } >
117+ { operation }
118+ </ span >
86119 ) }
87120 </ div >
88121
89- { data . operation && (
90- < div className = "flex items-center gap-1.5 mt-1" >
91- < span className = "px-1.5 py-0.5 text-[9px] font-bold rounded bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 uppercase" >
92- { data . operation as string }
93- </ span >
122+ { /* Key in monospace with namespace prefix */ }
123+ { ( data . key || data . namespace ) && (
124+ < div className = "bg-gray-50 dark:bg-gray-800/50 rounded px-2 py-1 overflow-hidden" >
125+ < p className = "text-[10px] font-mono truncate" >
126+ { data . namespace && (
127+ < span className = "text-gray-400 dark:text-gray-500" >
128+ { data . namespace as string } /
129+ </ span >
130+ ) }
131+ < span className = "text-cyan-700 dark:text-cyan-300 font-semibold" >
132+ { ( data . key as string ) ?? '' }
133+ </ span >
134+ </ p >
94135 </ div >
95136 ) }
96137
97- { data . key && (
98- < p className = "text-[10px] text-cyan-600/70 dark:text-cyan-400/50 mt-1 truncate font-mono" >
99- { data . key as string }
100- </ p >
101- ) }
102-
103- { data . namespace && (
104- < p className = "text-[10px] text-text-muted dark:text-dark-text-muted mt-0.5 truncate" >
105- ns: { data . namespace as string }
106- </ p >
107- ) }
108-
138+ { /* Error message */ }
109139 { status === 'error' && data . executionError && (
110- < p className = "text-xs text-error mt-1 truncate" title = { data . executionError as string } >
140+ < p className = "text-xs text-error truncate" title = { data . executionError as string } >
111141 { data . executionError as string }
112142 </ p >
113143 ) }
114144
145+ { /* Duration */ }
115146 { data . executionDuration != null && (
116- < p className = "text-[10px] text-text-muted dark:text-dark-text-muted mt-1 " >
147+ < p className = "text-[10px] text-text-muted dark:text-dark-text-muted" >
117148 { ( data . executionDuration as number ) < 1000
118149 ? `${ data . executionDuration } ms`
119150 : `${ ( ( data . executionDuration as number ) / 1000 ) . toFixed ( 1 ) } s` }
120151 </ p >
121152 ) }
122153 </ div >
123154
155+ { /* Output Handle */ }
124156 < Handle
125157 type = "source"
126158 position = { Position . Bottom }
0 commit comments