From 1369fc48496a8cef6b4dcf7a29cb248ffc76939b Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 22 Jan 2026 17:48:50 +0200 Subject: [PATCH] Fix issue with dropdown spilling over (#788) --- .../Task/TaskDetails/TaskDetailsHeader.tsx | 69 +++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/frontend/components/Task/TaskDetails/TaskDetailsHeader.tsx b/frontend/components/Task/TaskDetails/TaskDetailsHeader.tsx index 4794cde..b822b35 100644 --- a/frontend/components/Task/TaskDetails/TaskDetailsHeader.tsx +++ b/frontend/components/Task/TaskDetails/TaskDetailsHeader.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useEffect } from 'react'; +import React, { useRef, useState, useEffect, useLayoutEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { CheckIcon, @@ -60,9 +60,13 @@ const TaskDetailsHeader: React.FC = ({ const [isEditingTitle, setIsEditingTitle] = useState(false); const [editedTitle, setEditedTitle] = useState(task.name); const [actionsMenuOpen, setActionsMenuOpen] = useState(false); + const [actionsMenuStyle, setActionsMenuStyle] = + useState({}); + const [actionsMenuReady, setActionsMenuReady] = useState(false); const [priorityDropdownOpen, setPriorityDropdownOpen] = useState(false); const titleInputRef = useRef(null); const actionsMenuRef = useRef(null); + const actionsMenuDropdownRef = useRef(null); const priorityDropdownRef = useRef(null); useEffect(() => { @@ -76,6 +80,54 @@ const TaskDetailsHeader: React.FC = ({ } }, [isEditingTitle]); + useLayoutEffect(() => { + if (!actionsMenuOpen) { + setActionsMenuStyle({}); + setActionsMenuReady(false); + return; + } + + const updateMenuPosition = () => { + const triggerRect = actionsMenuRef.current?.getBoundingClientRect(); + const menuRect = + actionsMenuDropdownRef.current?.getBoundingClientRect(); + + if (!triggerRect || !menuRect) { + return; + } + + const padding = 8; + const gap = 8; + let top = triggerRect.bottom + gap; + if (top + menuRect.height + padding > window.innerHeight) { + top = triggerRect.top - menuRect.height - gap; + } + top = Math.min( + Math.max(top, padding), + window.innerHeight - menuRect.height - padding + ); + + let left = triggerRect.right - menuRect.width; + left = Math.min( + Math.max(left, padding), + window.innerWidth - menuRect.width - padding + ); + + setActionsMenuStyle({ + position: 'fixed', + top, + left, + }); + setActionsMenuReady(true); + }; + + updateMenuPosition(); + window.addEventListener('resize', updateMenuPosition); + return () => { + window.removeEventListener('resize', updateMenuPosition); + }; + }, [actionsMenuOpen]); + useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( @@ -264,7 +316,7 @@ const TaskDetailsHeader: React.FC = ({ {task.name} -
+
= ({ )} {formattedUpdatedAt && ( - + {t( 'task.updatedAt', 'Updated at' @@ -706,7 +758,16 @@ const TaskDetailsHeader: React.FC = ({ {actionsMenuOpen && ( -
+