diff --git a/src/components/GroupsOverlay.tsx b/src/components/GroupsOverlay.tsx index 6c2a0a1b..247adf24 100644 --- a/src/components/GroupsOverlay.tsx +++ b/src/components/GroupsOverlay.tsx @@ -66,6 +66,7 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { const [isEditing, setIsEditing] = useState(false); const [editName, setEditName] = useState(group?.name || ""); const [showColorPicker, setShowColorPicker] = useState(false); + const [showMenu, setShowMenu] = useState(false); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); const [resizeHandle, setResizeHandle] = useState(null); @@ -73,6 +74,7 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { const resizeStartRef = useRef<{ x: number; y: number; width: number; height: number; posX: number; posY: number } | null>(null); const inputRef = useRef(null); const colorPickerRef = useRef(null); + const menuRef = useRef(null); useEffect(() => { if (group?.name && !isEditing) { @@ -92,13 +94,16 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { if (colorPickerRef.current && !colorPickerRef.current.contains(e.target as Node)) { setShowColorPicker(false); } + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + setShowMenu(false); + } }; - if (showColorPicker) { + if (showColorPicker || showMenu) { document.addEventListener("mousedown", handleClickOutside); } return () => document.removeEventListener("mousedown", handleClickOutside); - }, [showColorPicker]); + }, [showColorPicker, showMenu]); const handleNameSubmit = useCallback(() => { if (editName.trim() && editName !== group?.name) { @@ -279,7 +284,7 @@ function GroupControls({ groupId, zoom }: GroupControlsProps) { overflow: "visible", }} > - {/* Floating group name label - top-left, viewport-scaled */} + {/* Group title label + three-dot menu - top-left, viewport-scaled */} {/* Outer wrapper: zero-height anchor at the top edge of the group */}
{/* Inner scaled element: bottom-anchored so it grows upward, scale keeps bottom-left fixed */}
- {isEditing ? ( - setEditName(e.target.value)} - onBlur={handleNameSubmit} - onKeyDown={handleKeyDown} - className="bg-transparent border-none outline-none text-xs font-medium text-white px-0 py-0" - style={{ minWidth: 60, maxWidth: 200, width: `${Math.max(60, editName.length * 7)}px` }} - /> - ) : ( - { e.stopPropagation(); setIsEditing(true); }} - > - {group.name} - - )} -
-
-
- - {/* Floating controls - top-right, scales naturally with canvas zoom */} -
-
- {/* Color Picker */} -
- - ); - })} + ) : ( + { e.stopPropagation(); setIsEditing(true); }} + > + {group.name} + + )} +
+ + {/* Expanded controls - shown when menu is open */} + {showMenu && ( +
+ {/* Color Picker */} +
+ + {showColorPicker && ( + <> + {/* Invisible backdrop to catch clicks outside */} +
setShowColorPicker(false)} + /> +
+ {COLOR_OPTIONS.map(({ color, label }, index) => { + const totalItems = COLOR_OPTIONS.length; + const arcSpread = 180; + const startAngle = -90 - arcSpread / 2; + const angleStep = arcSpread / (totalItems - 1); + const angle = startAngle + index * angleStep; + const radius = 55; + const rad = (angle * Math.PI) / 180; + const x = Math.cos(rad) * radius; + const y = Math.sin(rad) * radius; + const finalX = x - 12; + const finalY = y - 12; + + return ( + + ); + })} +
+ + )}
- - )} -
- {/* Lock/Unlock Button */} - + + {/* Delete Button */} + +
)} - - {/* Delete Button */} - + {/* Three-dot menu toggle */} + +