Browse Source

Improved archive card context menu

maziggy 5 months ago
parent
commit
5efb7ab2c8

+ 42 - 12
frontend/src/components/ContextMenu.tsx

@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState, useLayoutEffect } from 'react';
 import { ChevronRight } from 'lucide-react';
 import { ChevronRight } from 'lucide-react';
 
 
 export interface ContextMenuItem {
 export interface ContextMenuItem {
@@ -22,6 +22,8 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
   const menuRef = useRef<HTMLDivElement>(null);
   const menuRef = useRef<HTMLDivElement>(null);
   const [activeSubmenu, setActiveSubmenu] = useState<number | null>(null);
   const [activeSubmenu, setActiveSubmenu] = useState<number | null>(null);
   const submenuTimeoutRef = useRef<number | null>(null);
   const submenuTimeoutRef = useRef<number | null>(null);
+  const [position, setPosition] = useState({ x, y, visible: false });
+  const [openSubmenuLeft, setOpenSubmenuLeft] = useState(false);
 
 
   useEffect(() => {
   useEffect(() => {
     const handleClickOutside = (e: MouseEvent) => {
     const handleClickOutside = (e: MouseEvent) => {
@@ -54,25 +56,47 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
     };
     };
   }, [onClose]);
   }, [onClose]);
 
 
-  // Adjust position to keep menu in viewport
-  useEffect(() => {
+  // Adjust position to keep menu in viewport - use useLayoutEffect for synchronous measurement
+  useLayoutEffect(() => {
     if (menuRef.current) {
     if (menuRef.current) {
+      // Force a reflow to get accurate measurements
+      menuRef.current.style.visibility = 'hidden';
+      menuRef.current.style.display = 'block';
+
       const rect = menuRef.current.getBoundingClientRect();
       const rect = menuRef.current.getBoundingClientRect();
       const viewportWidth = window.innerWidth;
       const viewportWidth = window.innerWidth;
       const viewportHeight = window.innerHeight;
       const viewportHeight = window.innerHeight;
+      const padding = 8;
 
 
       let adjustedX = x;
       let adjustedX = x;
       let adjustedY = y;
       let adjustedY = y;
 
 
-      if (x + rect.width > viewportWidth) {
-        adjustedX = viewportWidth - rect.width - 8;
+      // Adjust horizontal position - if menu would overflow right, shift left
+      if (x + rect.width > viewportWidth - padding) {
+        adjustedX = Math.max(padding, viewportWidth - rect.width - padding);
+      }
+      // Also check if starting position is negative
+      if (adjustedX < padding) {
+        adjustedX = padding;
+      }
+
+      // Adjust vertical position - if menu would overflow bottom, shift up
+      if (y + rect.height > viewportHeight - padding) {
+        adjustedY = Math.max(padding, viewportHeight - rect.height - padding);
       }
       }
-      if (y + rect.height > viewportHeight) {
-        adjustedY = viewportHeight - rect.height - 8;
+      // Also check if starting position is negative
+      if (adjustedY < padding) {
+        adjustedY = padding;
       }
       }
 
 
-      menuRef.current.style.left = `${adjustedX}px`;
-      menuRef.current.style.top = `${adjustedY}px`;
+      // Check if submenus should open to the left (more space on left than right)
+      const submenuWidth = 180;
+      const spaceOnRight = viewportWidth - adjustedX - rect.width;
+      const spaceOnLeft = adjustedX;
+      // Only open left if there's not enough space on right AND there's enough space on left
+      setOpenSubmenuLeft(spaceOnRight < submenuWidth && spaceOnLeft > submenuWidth);
+
+      setPosition({ x: adjustedX, y: adjustedY, visible: true });
     }
     }
   }, [x, y]);
   }, [x, y]);
 
 
@@ -93,8 +117,12 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
   return (
   return (
     <div
     <div
       ref={menuRef}
       ref={menuRef}
-      className="fixed z-50 min-w-[180px] bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg shadow-xl py-1"
-      style={{ left: x, top: y }}
+      className="fixed z-50 min-w-[180px] max-w-[280px] bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg shadow-xl py-1"
+      style={{
+        left: position.x,
+        top: position.y,
+        visibility: position.visible ? 'visible' : 'hidden'
+      }}
     >
     >
       {items.map((item, index) => {
       {items.map((item, index) => {
         if (item.divider) {
         if (item.divider) {
@@ -136,7 +164,9 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
             {/* Submenu */}
             {/* Submenu */}
             {hasSubmenu && activeSubmenu === index && (
             {hasSubmenu && activeSubmenu === index && (
               <div
               <div
-                className="absolute left-full top-0 ml-1 min-w-[160px] bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg shadow-xl py-1 overflow-hidden max-h-[300px] overflow-y-auto z-[60]"
+                className={`absolute top-0 min-w-[160px] bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg shadow-xl py-1 overflow-hidden max-h-[300px] overflow-y-auto z-[60] ${
+                  openSubmenuLeft ? 'right-full mr-1' : 'left-full ml-1'
+                }`}
                 onMouseEnter={() => {
                 onMouseEnter={() => {
                   if (submenuTimeoutRef.current) {
                   if (submenuTimeoutRef.current) {
                     clearTimeout(submenuTimeoutRef.current);
                     clearTimeout(submenuTimeoutRef.current);

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-93kTpHtK.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BxbiDdlQ.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-CB_jd89Z.css


+ 2 - 2
static/index.html

@@ -23,8 +23,8 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-DgzHkxFp.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-CB_jd89Z.css">
+    <script type="module" crossorigin src="/assets/index-BxbiDdlQ.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-93kTpHtK.css">
   </head>
   </head>
   <body>
   <body>
     <div id="root"></div>
     <div id="root"></div>

Some files were not shown because too many files changed in this diff