Skip to content

Commit 5e0846c

Browse files
committed
Enhance relationship type selector with unique colors and premium hover states
- Changed EXTENDS color from violet to pink for better distinction from RELATES_TO - Added static Tailwind classes for all 16 relationship types to ensure proper color compilation - Implemented colored hover states matching each relationship's icon color - Added smart tooltip positioning (left-aligned for first column, right-aligned for last column, centered for middle) - All relationship boxes now show colored borders and backgrounds on hover, matching priority box behavior - Fixed dynamic class generation issue by using static color maps All 16 relationship types now have unique, distinct colors: DEFAULT_EDGE (gray), DEPENDS_ON (emerald), BLOCKS (rose), ENABLES (green), RELATES_TO (purple), IS_PART_OF (orange), FOLLOWS (indigo), PARALLEL_WITH (teal), DUPLICATES (yellow), CONFLICTS_WITH (red), VALIDATES (lime), REFERENCES (fuchsia), CONTAINS (blue), SUPERSEDES (cyan), EXTENDS (pink), TRIGGERS (sky)
1 parent 5ef0548 commit 5e0846c

2 files changed

Lines changed: 173 additions & 49 deletions

File tree

packages/web/src/components/CreateWorkItemModal.tsx

Lines changed: 119 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -377,45 +377,136 @@ export function CreateWorkItemModal({ isOpen, onClose, parentWorkItemId, positio
377377
<div className="max-h-[70vh] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-600 scrollbar-track-gray-800 hover:scrollbar-thumb-gray-500">
378378
<form onSubmit={handleSubmit} className="px-6 py-6 space-y-5 relative">
379379
{parentWorkItemId && (
380-
<div className="space-y-2 mb-2">
381-
<div className="bg-gradient-to-br from-gray-800/30 to-gray-700/20 border border-gray-600/30 rounded-lg p-2 shadow-lg backdrop-blur-sm">
382-
<div className="flex items-center space-x-2 mb-1">
383-
<p className="text-xs font-semibold text-blue-200">Connection Setup</p>
380+
<div className="space-y-4 mb-4">
381+
<div className="bg-gradient-to-br from-gray-800/40 to-gray-700/30 border border-gray-600/40 rounded-xl p-4 shadow-lg backdrop-blur-sm">
382+
<div className="flex items-center space-x-2 mb-2">
383+
<p className="text-sm font-semibold text-blue-200">Connection Setup</p>
384384
</div>
385-
<p className="text-xs text-blue-100 leading-relaxed">
385+
<p className="text-sm text-blue-100 leading-relaxed">
386386
A new work item will be created and automatically connected with your selected relationship type.
387387
</p>
388388
</div>
389389

390390
<div>
391-
<div className="flex items-center space-x-2 mb-1">
391+
<div className="flex items-center space-x-2 mb-3">
392392
<label className="text-base font-bold text-gray-100 tracking-wide">
393393
Relationship Type
394394
</label>
395395
</div>
396-
<div className="grid grid-cols-2 gap-2">
397-
{RELATIONSHIP_OPTIONS.map((relation) => (
398-
<button
399-
key={relation.type}
400-
type="button"
401-
onClick={() => setSelectedRelationType(relation.type)}
402-
className={`p-2 rounded-lg text-left transition-all duration-200 border group ${
403-
selectedRelationType === relation.type
404-
? 'bg-gradient-to-br from-blue-600/20 via-blue-700/25 to-blue-800/20 border-blue-400/50 shadow-lg shadow-blue-500/10'
405-
: 'bg-gradient-to-br from-gray-700/30 to-gray-800/30 hover:from-gray-600/40 hover:to-gray-700/40 border-gray-600/30 hover:border-gray-500/50 hover:shadow-md'
406-
}`}
407-
>
408-
<div className="flex items-center space-x-1.5 mb-0.5">
409-
{getRelationshipIconElement(relation.type, `h-4 w-4 ${selectedRelationType === relation.type ? 'text-blue-400' : ''}`)}
410-
<span className={`font-medium text-xs ${selectedRelationType === relation.type ? 'text-blue-400' : relation.color}`}>
411-
{relation.label}
412-
</span>
396+
<div className="grid grid-cols-4 gap-3">
397+
{RELATIONSHIP_OPTIONS.map((relation, index) => {
398+
const isSelected = selectedRelationType === relation.type;
399+
const shadowClass = relation.hexColor;
400+
401+
// Static Tailwind classes for each relationship type
402+
const getSelectedStyles = (type: RelationshipType) => {
403+
const styles: Record<RelationshipType, string> = {
404+
'DEFAULT_EDGE': 'border-gray-400 bg-gradient-to-br from-gray-400/20 to-gray-400/10',
405+
'DEPENDS_ON': 'border-emerald-400 bg-gradient-to-br from-emerald-400/20 to-emerald-400/10',
406+
'BLOCKS': 'border-rose-400 bg-gradient-to-br from-rose-400/20 to-rose-400/10',
407+
'ENABLES': 'border-green-400 bg-gradient-to-br from-green-400/20 to-green-400/10',
408+
'RELATES_TO': 'border-purple-400 bg-gradient-to-br from-purple-400/20 to-purple-400/10',
409+
'IS_PART_OF': 'border-orange-400 bg-gradient-to-br from-orange-400/20 to-orange-400/10',
410+
'FOLLOWS': 'border-indigo-400 bg-gradient-to-br from-indigo-400/20 to-indigo-400/10',
411+
'PARALLEL_WITH': 'border-teal-400 bg-gradient-to-br from-teal-400/20 to-teal-400/10',
412+
'DUPLICATES': 'border-yellow-400 bg-gradient-to-br from-yellow-400/20 to-yellow-400/10',
413+
'CONFLICTS_WITH': 'border-red-500 bg-gradient-to-br from-red-500/20 to-red-500/10',
414+
'VALIDATES': 'border-lime-400 bg-gradient-to-br from-lime-400/20 to-lime-400/10',
415+
'REFERENCES': 'border-fuchsia-400 bg-gradient-to-br from-fuchsia-400/20 to-fuchsia-400/10',
416+
'CONTAINS': 'border-blue-400 bg-gradient-to-br from-blue-400/20 to-blue-400/10',
417+
'SUPERSEDES': 'border-cyan-400 bg-gradient-to-br from-cyan-400/20 to-cyan-400/10',
418+
'EXTENDS': 'border-pink-400 bg-gradient-to-br from-pink-400/20 to-pink-400/10',
419+
'TRIGGERS': 'border-sky-300 bg-gradient-to-br from-sky-300/20 to-sky-300/10',
420+
};
421+
return styles[type] || styles.DEFAULT_EDGE;
422+
};
423+
424+
const getHoverStyles = (type: RelationshipType) => {
425+
const hoverStyles: Record<RelationshipType, string> = {
426+
'DEFAULT_EDGE': 'hover:border-gray-400/70 hover:bg-gray-400/10',
427+
'DEPENDS_ON': 'hover:border-emerald-400/70 hover:bg-emerald-400/10',
428+
'BLOCKS': 'hover:border-rose-400/70 hover:bg-rose-400/10',
429+
'ENABLES': 'hover:border-green-400/70 hover:bg-green-400/10',
430+
'RELATES_TO': 'hover:border-purple-400/70 hover:bg-purple-400/10',
431+
'IS_PART_OF': 'hover:border-orange-400/70 hover:bg-orange-400/10',
432+
'FOLLOWS': 'hover:border-indigo-400/70 hover:bg-indigo-400/10',
433+
'PARALLEL_WITH': 'hover:border-teal-400/70 hover:bg-teal-400/10',
434+
'DUPLICATES': 'hover:border-yellow-400/70 hover:bg-yellow-400/10',
435+
'CONFLICTS_WITH': 'hover:border-red-500/70 hover:bg-red-500/10',
436+
'VALIDATES': 'hover:border-lime-400/70 hover:bg-lime-400/10',
437+
'REFERENCES': 'hover:border-fuchsia-400/70 hover:bg-fuchsia-400/10',
438+
'CONTAINS': 'hover:border-blue-400/70 hover:bg-blue-400/10',
439+
'SUPERSEDES': 'hover:border-cyan-400/70 hover:bg-cyan-400/10',
440+
'EXTENDS': 'hover:border-pink-400/70 hover:bg-pink-400/10',
441+
'TRIGGERS': 'hover:border-sky-300/70 hover:bg-sky-300/10',
442+
};
443+
return hoverStyles[type] || hoverStyles.DEFAULT_EDGE;
444+
};
445+
446+
// Determine tooltip position based on column (4-column grid)
447+
const columnIndex = index % 4;
448+
const isFirstColumn = columnIndex === 0;
449+
const isLastColumn = columnIndex === 3;
450+
451+
const tooltipPositionClass = isFirstColumn
452+
? 'left-0'
453+
: isLastColumn
454+
? 'right-0'
455+
: 'left-1/2 -translate-x-1/2';
456+
457+
const arrowPositionClass = isFirstColumn
458+
? 'left-4'
459+
: isLastColumn
460+
? 'right-4'
461+
: 'left-1/2 -translate-x-1/2';
462+
463+
return (
464+
<div key={relation.type} className="relative group/tooltip">
465+
<button
466+
type="button"
467+
onClick={() => setSelectedRelationType(relation.type)}
468+
className={`w-full p-3 rounded-xl transition-all duration-300 border-2 backdrop-blur-sm relative overflow-hidden ${
469+
isSelected
470+
? `${getSelectedStyles(relation.type)} shadow-lg scale-105`
471+
: `border-gray-600/50 bg-gradient-to-br from-gray-700/40 to-gray-800/40 ${getHoverStyles(relation.type)} hover:shadow-lg hover:scale-105 active:scale-95`
472+
}`}
473+
style={{
474+
animationDelay: `${index * 20}ms`,
475+
...(isSelected && { boxShadow: `0 10px 25px -5px ${shadowClass}40, 0 8px 10px -6px ${shadowClass}30` })
476+
}}
477+
>
478+
<div className="flex flex-col items-center justify-center space-y-1.5">
479+
<div className={`transition-all duration-300 ${isSelected ? 'scale-110' : 'group-hover:scale-110'}`}>
480+
{getRelationshipIconElement(relation.type, `h-5 w-5`)}
481+
</div>
482+
<span className={`font-semibold text-[10px] text-center leading-tight transition-colors duration-300 ${
483+
isSelected ? relation.color : 'text-gray-300 group-hover:text-white'
484+
}`}>
485+
{relation.label}
486+
</span>
487+
</div>
488+
{isSelected && (
489+
<div
490+
className="absolute top-1 right-1 w-4 h-4 text-white rounded-full flex items-center justify-center text-[8px] shadow-lg animate-in zoom-in duration-200"
491+
style={{ background: shadowClass }}
492+
>
493+
494+
</div>
495+
)}
496+
</button>
497+
498+
{/* Premium Tooltip on Hover */}
499+
<div className={`absolute bottom-full ${tooltipPositionClass} mb-2 px-3 py-2 bg-gradient-to-br from-gray-900/98 to-black/98 backdrop-blur-xl rounded-lg border border-gray-600/50 shadow-2xl opacity-0 invisible group-hover/tooltip:opacity-100 group-hover/tooltip:visible transition-all duration-300 pointer-events-none z-50 whitespace-nowrap`}>
500+
<div className="text-xs text-gray-300 leading-relaxed">
501+
{relation.description}
502+
</div>
503+
<div className={`absolute top-full ${arrowPositionClass} -mt-px`}>
504+
<div className="border-4 border-transparent border-t-gray-900/98"></div>
505+
</div>
506+
</div>
413507
</div>
414-
<p className="text-[10px] text-gray-400">
415-
{relation.description}
416-
</p>
417-
</button>
418-
))}
508+
);
509+
})}
419510
</div>
420511
</div>
421512
</div>

packages/web/src/constants/workItemConstants.tsx

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import React from 'react';
2-
import {
3-
ClipboardList,
4-
Calendar,
5-
Clock,
6-
CheckCircle,
2+
import {
3+
ClipboardList,
4+
Calendar,
5+
Clock,
6+
CheckCircle,
77
AlertCircle,
88
XCircle,
99
Eye,
1010
Pause,
11-
Layers,
12-
Trophy,
13-
Target,
14-
Sparkles,
15-
ListTodo,
16-
AlertTriangle,
17-
Lightbulb,
11+
Layers,
12+
Trophy,
13+
Target,
14+
Sparkles,
15+
ListTodo,
16+
AlertTriangle,
17+
Lightbulb,
1818
Microscope,
1919
Flame,
2020
Zap,
@@ -33,6 +33,9 @@ import {
3333
Shield,
3434
Bookmark,
3535
Package,
36+
Replace,
37+
Maximize2,
38+
Play,
3639
// Default Icon
3740
Square,
3841
Paperclip,
@@ -85,7 +88,10 @@ export {
8588
Shield,
8689
Bookmark,
8790
Package,
88-
91+
Replace,
92+
Maximize2,
93+
Play,
94+
8995
// Default Icon
9096
Square,
9197
Paperclip
@@ -605,9 +611,9 @@ export const isValidPriorityLevel = (priority: string): priority is PriorityLeve
605611
// RELATIONSHIP TYPES SYSTEM
606612
// ============================
607613

608-
// All possible relationship types between work items
614+
// All possible relationship types between work items (16 types total)
609615
// These define how nodes connect to each other in the graph
610-
export type RelationshipType =
616+
export type RelationshipType =
611617
| 'DEPENDS_ON' // Source needs target to be completed first
612618
| 'BLOCKS' // Source prevents target from progressing
613619
| 'ENABLES' // Source makes target easier/possible
@@ -620,6 +626,9 @@ export type RelationshipType =
620626
| 'VALIDATES' // Source tests/validates target
621627
| 'REFERENCES' // Source references/cites target
622628
| 'CONTAINS' // Source contains/encompasses target
629+
| 'SUPERSEDES' // Source replaces target
630+
| 'EXTENDS' // Source extends functionality of target
631+
| 'TRIGGERS' // Source triggers/activates target
623632
| 'DEFAULT_EDGE'; // Generic connection
624633

625634
export interface RelationshipOption {
@@ -653,8 +662,8 @@ export const RELATIONSHIP_TYPES: Record<RelationshipType, RelationshipOption> =
653662
label: 'Blocks',
654663
description: 'Source node blocks target node',
655664
icon: Ban,
656-
color: 'text-red-400',
657-
hexColor: '#f87171'
665+
color: 'text-rose-400',
666+
hexColor: '#fb7185'
658667
},
659668
ENABLES: {
660669
type: 'ENABLES',
@@ -717,16 +726,16 @@ export const RELATIONSHIP_TYPES: Record<RelationshipType, RelationshipOption> =
717726
label: 'Validates',
718727
description: 'Source node validates target node',
719728
icon: Shield,
720-
color: 'text-emerald-400',
721-
hexColor: '#34d399'
729+
color: 'text-lime-400',
730+
hexColor: '#a3e635'
722731
},
723732
REFERENCES: {
724733
type: 'REFERENCES',
725734
label: 'References',
726735
description: 'Source node references target node',
727736
icon: Bookmark,
728-
color: 'text-slate-400',
729-
hexColor: '#94a3b8'
737+
color: 'text-fuchsia-400',
738+
hexColor: '#e879f9'
730739
},
731740
CONTAINS: {
732741
type: 'CONTAINS',
@@ -736,6 +745,30 @@ export const RELATIONSHIP_TYPES: Record<RelationshipType, RelationshipOption> =
736745
color: 'text-blue-400',
737746
hexColor: '#60a5fa'
738747
},
748+
SUPERSEDES: {
749+
type: 'SUPERSEDES',
750+
label: 'Supersedes',
751+
description: 'Source node replaces target node',
752+
icon: Replace,
753+
color: 'text-cyan-400',
754+
hexColor: '#22d3ee'
755+
},
756+
EXTENDS: {
757+
type: 'EXTENDS',
758+
label: 'Extends',
759+
description: 'Source node extends functionality of target',
760+
icon: Maximize2,
761+
color: 'text-pink-400',
762+
hexColor: '#f472b6'
763+
},
764+
TRIGGERS: {
765+
type: 'TRIGGERS',
766+
label: 'Triggers',
767+
description: 'Source node triggers/activates target',
768+
icon: Play,
769+
color: 'text-sky-300',
770+
hexColor: '#7dd3fc'
771+
},
739772
};
740773

741774
// ============================

0 commit comments

Comments
 (0)