Skip to content

Commit f9ccca7

Browse files
committed
Enhance: Modernize Create Relationship dialog with 4-column grid and premium UI
- Changed grid from 3 to 4 columns for better use of space - Added gradient backgrounds with unique colors per relationship type - Implemented smooth scale and fade-in animations with staggered delays - Enhanced hover states with transform scale effects - Added pulsing effect for selected relationship types - Improved header with gradient amber styling - Repositioned dialog to right side to avoid overlapping node menus - Added optimistic responses for instant feedback on create/update/delete - Increased dialog width from 450px to 580px to accommodate 4 columns - Enhanced status indicators with colored badges for source/target nodes
1 parent 3437b76 commit f9ccca7

1 file changed

Lines changed: 181 additions & 50 deletions

File tree

packages/web/src/components/RelationshipEditorWindow.tsx

Lines changed: 181 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,17 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
9595

9696
// Initialize position and auto-calculate height based on content
9797
useEffect(() => {
98-
const sidebarWidth = getComputedStyle(document.documentElement).getPropertyValue('--sidebar-width') || '16rem';
99-
const sidebarPixels = sidebarWidth === '4rem' ? 64 : 256;
100-
101-
setPosition({
102-
x: sidebarPixels + 16,
103-
y: 180 // Position similar to the old fixed panel
98+
const windowWidth = window.innerWidth;
99+
const dialogWidth = isExpanded ? 580 : 450;
100+
101+
setPosition({
102+
x: windowWidth - dialogWidth - 24,
103+
y: 100
104104
});
105-
105+
106106
const contentHeight = calculateContentHeight();
107107
setSize({
108-
width: isExpanded ? 450 : 350,
108+
width: dialogWidth,
109109
height: contentHeight
110110
});
111111
}, [isExpanded, isVisible, editingEdge, selectedNodes.size]);
@@ -120,7 +120,7 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
120120
});
121121
}
122122
if (isResizing) {
123-
const minWidth = 350;
123+
const minWidth = 450;
124124
const minHeight = calculateContentHeight(); // Use content-based minimum height
125125
const newWidth = Math.max(minWidth, resizeStart.width + (e.clientX - resizeStart.x));
126126
const newHeight = Math.max(minHeight, resizeStart.height + (e.clientY - resizeStart.y));
@@ -177,6 +177,20 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
177177
variables: {
178178
where: { id: editingEdge.edge.id },
179179
update: { type: newType }
180+
},
181+
optimisticResponse: {
182+
__typename: 'Mutation',
183+
updateEdges: {
184+
__typename: 'UpdateEdgesMutationResponse',
185+
edges: [{
186+
__typename: 'Edge',
187+
id: editingEdge.edge.id,
188+
type: newType,
189+
weight: editingEdge.edge.strength || 1.0,
190+
source: editingEdge.edge.source,
191+
target: editingEdge.edge.target
192+
}]
193+
}
180194
}
181195
});
182196
showSuccess('Relationship Updated', 'Edge type changed successfully');
@@ -196,6 +210,31 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
196210
target: { connect: { where: { node: { id: targetNode.id } } } },
197211
weight: 1.0
198212
}]
213+
},
214+
optimisticResponse: {
215+
__typename: 'Mutation',
216+
createEdges: {
217+
__typename: 'CreateEdgesMutationResponse',
218+
edges: [{
219+
__typename: 'Edge',
220+
id: `temp-${Date.now()}`,
221+
type: newType,
222+
weight: 1.0,
223+
source: {
224+
__typename: 'WorkItem',
225+
id: sourceNode.id,
226+
title: sourceNode.title,
227+
type: sourceNode.type
228+
},
229+
target: {
230+
__typename: 'WorkItem',
231+
id: targetNode.id,
232+
title: targetNode.title,
233+
type: targetNode.type
234+
},
235+
createdAt: new Date().toISOString()
236+
}]
237+
}
199238
}
200239
});
201240
showSuccess('Relationship Created', 'New relationship added successfully');
@@ -257,10 +296,18 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
257296
// Handle edge delete
258297
const handleDeleteEdge = async () => {
259298
if (!editingEdge) return;
260-
299+
261300
try {
262301
await deleteEdgeMutation({
263-
variables: { where: { id: editingEdge.edge.id } }
302+
variables: { where: { id: editingEdge.edge.id } },
303+
optimisticResponse: {
304+
__typename: 'Mutation',
305+
deleteEdges: {
306+
__typename: 'DeleteInfo',
307+
nodesDeleted: 0,
308+
relationshipsDeleted: 1
309+
}
310+
}
264311
});
265312
showSuccess('Relationship Deleted', 'Edge removed successfully');
266313
refetchEdges?.();
@@ -277,9 +324,22 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
277324
const isEditingMode = !!editingEdge;
278325

279326
return createPortal(
280-
<div
327+
<>
328+
<style>{`
329+
@keyframes fadeInScale {
330+
from {
331+
opacity: 0;
332+
transform: scale(0.9);
333+
}
334+
to {
335+
opacity: 1;
336+
transform: scale(1);
337+
}
338+
}
339+
`}</style>
340+
<div
281341
ref={windowRef}
282-
className="fixed bg-white/5 backdrop-blur-xl border border-white/20 rounded-xl shadow-lg overflow-hidden z-50"
342+
className="fixed bg-black/90 backdrop-blur-xl border border-white/10 rounded-2xl shadow-2xl overflow-hidden z-[60]"
283343
style={{
284344
left: `${position.x}px`,
285345
top: `${position.y}px`,
@@ -291,28 +351,30 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
291351
onClick={(e) => e.stopPropagation()}
292352
>
293353
{/* Header */}
294-
<div
295-
className="flex items-center justify-between p-3 bg-white/10 border-b border-white/10 cursor-move select-none"
354+
<div
355+
className="flex items-center justify-between p-4 bg-gradient-to-r from-amber-500/10 to-orange-500/10 border-b border-amber-500/20 cursor-move select-none backdrop-blur-sm"
296356
onMouseDown={handleDragStart}
297357
>
298358
<div className="flex items-center space-x-2">
299-
<Zap className="h-4 w-4 text-amber-400" />
359+
<div className="p-1.5 bg-amber-500/20 rounded-lg">
360+
<Zap className="h-4 w-4 text-amber-400" />
361+
</div>
300362
<span className="text-white font-semibold text-sm">
301363
{isEditingMode ? 'Edit Relationship' : 'Create Relationship'}
302364
</span>
303365
</div>
304366
<div className="flex items-center space-x-1">
305367
<button
306368
onClick={() => setIsExpanded(!isExpanded)}
307-
className="p-1 rounded text-gray-400 hover:text-white hover:bg-white/10 transition-colors"
369+
className="p-1.5 rounded-lg text-gray-400 hover:text-white hover:bg-white/10 transition-all duration-200"
308370
>
309-
{isExpanded ? <Minimize2 className="h-3 w-3" /> : <Maximize2 className="h-3 w-3" />}
371+
{isExpanded ? <Minimize2 className="h-3.5 w-3.5" /> : <Maximize2 className="h-3.5 w-3.5" />}
310372
</button>
311373
<button
312374
onClick={onClose}
313-
className="p-1 rounded text-gray-400 hover:text-white hover:bg-white/10 transition-colors"
375+
className="p-1.5 rounded-lg text-gray-400 hover:text-white hover:bg-red-500/20 transition-all duration-200"
314376
>
315-
<X className="h-3 w-3" />
377+
<X className="h-3.5 w-3.5" />
316378
</button>
317379
</div>
318380
</div>
@@ -321,61 +383,129 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
321383
{isExpanded && (
322384
<div className="p-4 space-y-4">
323385
{/* Status indicator */}
324-
<div className="text-center">
386+
<div className="text-center space-y-2">
325387
{!canEdit && (
326-
<div className="text-gray-400 text-sm">
327-
Select two nodes or click on an edge to edit relationships
388+
<div className="space-y-1">
389+
<div className="text-gray-300 text-sm font-medium">
390+
{selectedNodeObjects.length === 0 && 'No nodes selected'}
391+
{selectedNodeObjects.length === 1 && 'One node selected'}
392+
</div>
393+
<div className="text-gray-400 text-xs">
394+
{selectedNodeObjects.length === 0 && 'Select two nodes to create a relationship'}
395+
{selectedNodeObjects.length === 1 && 'Select one more node to create a relationship'}
396+
</div>
397+
{selectedNodeObjects.length === 1 && (
398+
<div className="mt-2 inline-block px-3 py-1 bg-white/10 rounded-lg text-white text-xs">
399+
{selectedNodeObjects[0].title}
400+
</div>
401+
)}
328402
</div>
329403
)}
330404
{canEdit && !isEditingMode && (
331-
<div className="text-green-400 text-sm">
332-
Ready to create relationship between {selectedNodeObjects.length} selected nodes
405+
<div className="space-y-2">
406+
<div className="text-green-400 text-sm font-medium">
407+
Ready to create relationship
408+
</div>
409+
<div className="flex items-center justify-center gap-2 text-xs">
410+
<div className="px-3 py-1 bg-green-500/20 border border-green-500/30 rounded-lg text-green-300">
411+
{selectedNodeObjects[0].title}
412+
</div>
413+
<span className="text-gray-400"></span>
414+
<div className="px-3 py-1 bg-green-500/20 border border-green-500/30 rounded-lg text-green-300">
415+
{selectedNodeObjects[1].title}
416+
</div>
417+
</div>
418+
<div className="text-gray-400 text-xs">
419+
Select a relationship type below
420+
</div>
333421
</div>
334422
)}
335423
{isEditingMode && (
336-
<div className="text-blue-400 text-sm">
337-
Editing relationship: {(() => {
338-
const sourceId = typeof editingEdge?.edge.source === 'string'
339-
? editingEdge?.edge.source
340-
: (editingEdge?.edge.source as any)?.id;
341-
const targetId = typeof editingEdge?.edge.target === 'string'
342-
? editingEdge?.edge.target
343-
: (editingEdge?.edge.target as any)?.id;
344-
345-
const sourceTitle = workItems.find(item => item.id === sourceId)?.title || 'Unknown';
346-
const targetTitle = workItems.find(item => item.id === targetId)?.title || 'Unknown';
347-
348-
return `${sourceTitle}${targetTitle}`;
349-
})()}
424+
<div className="space-y-2">
425+
<div className="text-blue-400 text-sm font-medium">
426+
Editing Relationship
427+
</div>
428+
<div className="flex items-center justify-center gap-2 text-xs">
429+
{(() => {
430+
const sourceId = typeof editingEdge?.edge.source === 'string'
431+
? editingEdge?.edge.source
432+
: (editingEdge?.edge.source as any)?.id;
433+
const targetId = typeof editingEdge?.edge.target === 'string'
434+
? editingEdge?.edge.target
435+
: (editingEdge?.edge.target as any)?.id;
436+
437+
const sourceTitle = workItems.find(item => item.id === sourceId)?.title || 'Unknown';
438+
const targetTitle = workItems.find(item => item.id === targetId)?.title || 'Unknown';
439+
440+
return (
441+
<>
442+
<div className="px-3 py-1 bg-blue-500/20 border border-blue-500/30 rounded-lg text-blue-300">
443+
{sourceTitle}
444+
</div>
445+
<span className="text-gray-400"></span>
446+
<div className="px-3 py-1 bg-blue-500/20 border border-blue-500/30 rounded-lg text-blue-300">
447+
{targetTitle}
448+
</div>
449+
</>
450+
);
451+
})()}
452+
</div>
350453
</div>
351454
)}
352455
</div>
353456

354457
{/* Relationship type grid */}
355458
{canEdit && (
356459
<div>
357-
<div className="text-white text-sm font-medium mb-3">Relationship Type</div>
358-
<div className="grid grid-cols-3 gap-2">
359-
{RELATIONSHIP_OPTIONS.map((option) => {
460+
<div className="text-white text-sm font-semibold mb-4 flex items-center gap-2">
461+
<span className="text-transparent bg-clip-text bg-gradient-to-r from-amber-400 to-orange-400">
462+
Relationship Type
463+
</span>
464+
</div>
465+
<div className="grid grid-cols-4 gap-2.5">
466+
{RELATIONSHIP_OPTIONS.map((option, index) => {
360467
const isSelected = isEditingMode ? editingEdge?.edge.type === option.type : false;
468+
const config = getRelationshipConfig(option.type);
361469
return (
362470
<button
363471
key={option.type}
364472
onClick={() => handleRelationshipTypeChange(option.type)}
365473
className={`
366-
group relative p-3 rounded-lg border transition-all duration-200
367-
${isSelected
368-
? 'border-amber-400 bg-amber-400/10 text-amber-400'
369-
: 'border-white/20 bg-white/5 text-white hover:border-white/40 hover:bg-white/10'
474+
group relative p-3 rounded-xl border transition-all duration-300 transform
475+
hover:scale-105 hover:z-10
476+
${isSelected
477+
? `border-${config.color}-400/50 bg-gradient-to-br from-${config.color}-500/30 via-${config.color}-500/20 to-${config.color}-500/10 shadow-lg shadow-${config.color}-500/20`
478+
: 'border-white/10 bg-gradient-to-br from-white/5 via-white/3 to-transparent hover:border-white/30 hover:from-white/10 hover:via-white/5'
370479
}
371480
`}
481+
style={{
482+
animation: `fadeInScale 0.4s ease-out ${index * 0.03}s both`,
483+
...(isSelected && {
484+
borderColor: `${config.color === 'blue' ? 'rgb(96 165 250 / 0.5)' :
485+
config.color === 'red' ? 'rgb(248 113 113 / 0.5)' :
486+
config.color === 'green' ? 'rgb(74 222 128 / 0.5)' :
487+
config.color === 'purple' ? 'rgb(192 132 252 / 0.5)' :
488+
config.color === 'orange' ? 'rgb(251 146 60 / 0.5)' :
489+
config.color === 'teal' ? 'rgb(45 212 191 / 0.5)' :
490+
config.color === 'yellow' ? 'rgb(250 204 21 / 0.5)' :
491+
config.color === 'pink' ? 'rgb(244 114 182 / 0.5)' :
492+
config.color === 'indigo' ? 'rgb(129 140 248 / 0.5)' :
493+
config.color === 'cyan' ? 'rgb(103 232 249 / 0.5)' :
494+
'rgb(156 163 175 / 0.5)'}`
495+
})
496+
}}
372497
title={option.description}
373498
>
374-
<div className="flex flex-col items-center space-y-2">
375-
<div className={`text-lg group-hover:scale-110 transition-transform`}>
499+
{isSelected && (
500+
<div className="absolute inset-0 rounded-xl bg-gradient-to-br from-white/10 to-transparent opacity-50 animate-pulse"></div>
501+
)}
502+
<div className="relative flex flex-col items-center space-y-1.5">
503+
<div className={`text-lg transition-all duration-300 ${isSelected ? 'scale-110' : 'group-hover:scale-110'}`}>
376504
{getRelationshipIconElement(option.type, "h-5 w-5")}
377505
</div>
378-
<span className="text-xs font-medium text-center leading-tight">
506+
<span className={`text-[10px] font-semibold text-center leading-tight transition-colors ${
507+
isSelected ? 'text-white' : 'text-gray-300 group-hover:text-white'
508+
}`}>
379509
{option.label}
380510
</span>
381511
</div>
@@ -432,7 +562,8 @@ export const RelationshipEditorWindow: React.FC<RelationshipEditorWindowProps> =
432562
<div className="absolute bottom-1 right-1 w-2 h-2 border-r-2 border-b-2 border-white/40"></div>
433563
</div>
434564
)}
435-
</div>,
565+
</div>
566+
</>,
436567
document.body
437568
);
438569
};

0 commit comments

Comments
 (0)