fix(ui): adjust submenu placement based on available viewport space

- add refs for submenu trigger and panel elements
- compute available space above/below using getBoundingClientRect
- dynamically set submenu side to avoid overflow outside viewport
This commit is contained in:
2026-04-25 20:57:18 -04:00
parent 452bd51d46
commit 303042e749
@@ -159,6 +159,26 @@ function BlockActionsMenu({
const { side } = useDropdownPlacement(open, triggerRef); const { side } = useDropdownPlacement(open, triggerRef);
const [submenuOpen, setSubmenuOpen] = useState(false); const [submenuOpen, setSubmenuOpen] = useState(false);
const submenuTimerRef = useRef(null); const submenuTimerRef = useRef(null);
const submenuTriggerRef = useRef(null);
const submenuPanelRef = useRef(null);
const [submenuSide, setSubmenuSide] = useState('below');
useLayoutEffect(() => {
if (!submenuOpen) return;
const trigger = submenuTriggerRef.current;
const panel = submenuPanelRef.current;
if (!trigger || !panel || typeof window === 'undefined') return;
const triggerRect = trigger.getBoundingClientRect();
const panelHeight = panel.getBoundingClientRect().height;
const vh = window.innerHeight;
const spaceBelow = vh - triggerRect.top - DROPDOWN_VIEWPORT_MARGIN;
const spaceAbove = triggerRect.bottom - DROPDOWN_VIEWPORT_MARGIN;
if (panelHeight > spaceBelow && spaceAbove > spaceBelow) {
setSubmenuSide('above');
} else {
setSubmenuSide('below');
}
}, [submenuOpen, transformOptions]);
function scheduleSubmenuClose() { function scheduleSubmenuClose() {
if (submenuTimerRef.current) clearTimeout(submenuTimerRef.current); if (submenuTimerRef.current) clearTimeout(submenuTimerRef.current);
@@ -236,6 +256,7 @@ function BlockActionsMenu({
<div className="p-1.5 flex flex-col gap-0.5"> <div className="p-1.5 flex flex-col gap-0.5">
{transformOptions.length > 0 && ( {transformOptions.length > 0 && (
<div <div
ref={submenuTriggerRef}
className="relative" className="relative"
onMouseEnter={() => { cancelSubmenuClose(); setSubmenuOpen(true); }} onMouseEnter={() => { cancelSubmenuClose(); setSubmenuOpen(true); }}
onMouseLeave={scheduleSubmenuClose} onMouseLeave={scheduleSubmenuClose}
@@ -250,7 +271,8 @@ function BlockActionsMenu({
</div> </div>
{submenuOpen && ( {submenuOpen && (
<div <div
className="absolute left-full top-0 ml-1 w-56 rounded-xl border border-black/8 dark:border-white/8 bg-white dark:bg-[#0B0B0B] shadow-lg p-1.5 flex flex-col gap-0.5 z-50" ref={submenuPanelRef}
className={`absolute left-full ${submenuSide === 'above' ? 'bottom-0' : 'top-0'} ml-1 w-56 rounded-xl border border-black/8 dark:border-white/8 bg-white dark:bg-[#0B0B0B] shadow-lg p-1.5 flex flex-col gap-0.5 z-50`}
onMouseEnter={cancelSubmenuClose} onMouseEnter={cancelSubmenuClose}
onMouseLeave={scheduleSubmenuClose} onMouseLeave={scheduleSubmenuClose}
> >