fix: prevent context menu jump and animation on open

This commit is contained in:
Linus Rath
2026-05-01 17:43:07 +02:00
parent 0d9fa0285f
commit 4af952613a
2 changed files with 54 additions and 34 deletions
+2 -28
View File
@@ -1,6 +1,6 @@
"use client";
import { useState, useCallback, useEffect, useLayoutEffect, useRef } from "react";
import { useState, useCallback, useEffect, useRef } from "react";
interface Position {
x: number;
@@ -21,7 +21,7 @@ interface UseContextMenuReturn<T> {
}
const MENU_WIDTH = 200;
const MENU_HEIGHT = 320; // Initial estimate; refined after mount via layout effect
const MENU_HEIGHT = 320; // Approximate max height
const VIEWPORT_MARGIN = 10;
export function useContextMenu<T>(): UseContextMenuReturn<T> {
@@ -57,32 +57,6 @@ export function useContextMenu<T>(): UseContextMenuReturn<T> {
return { x, y };
}, []);
// Re-clamp position once we can measure the actual rendered menu — the
// initial estimate uses a fixed height which can be too small for menus
// with many items, causing the bottom to be clipped off-screen.
useLayoutEffect(() => {
if (!contextMenu.isOpen || !menuRef.current) return;
const rect = menuRef.current.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let x = contextMenu.position.x;
let y = contextMenu.position.y;
if (x + rect.width > viewportWidth - VIEWPORT_MARGIN) {
x = viewportWidth - rect.width - VIEWPORT_MARGIN;
}
if (y + rect.height > viewportHeight - VIEWPORT_MARGIN) {
y = viewportHeight - rect.height - VIEWPORT_MARGIN;
}
x = Math.max(VIEWPORT_MARGIN, x);
y = Math.max(VIEWPORT_MARGIN, y);
if (x !== contextMenu.position.x || y !== contextMenu.position.y) {
setContextMenu((prev) => ({ ...prev, position: { x, y } }));
}
}, [contextMenu.isOpen, contextMenu.position.x, contextMenu.position.y]);
const openContextMenu = useCallback((e: React.MouseEvent, data: T) => {
e.preventDefault();
e.stopPropagation();