From 7ea2cb4ab25a31446b071e9ee7c42c56947667bc Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Wed, 15 Jul 2026 14:07:47 +0330 Subject: [PATCH] fix: render inventory actions menu in a portal so it is not clipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The «عملیات» dropdown was absolutely positioned inside the table container, which has overflow:hidden for its rounded corners, so the menu was clipped below the table. Render it through a Portal with fixed positioning computed from the trigger's rect (RTL-aligned to the trigger's right edge), tracking scroll/resize while open. Co-Authored-By: Claude Opus 4.8 --- .../inventory/InventoryActionsMenu.tsx | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/assets/admin/components/inventory/InventoryActionsMenu.tsx b/assets/admin/components/inventory/InventoryActionsMenu.tsx index 7eaaec10..29b7fee6 100644 --- a/assets/admin/components/inventory/InventoryActionsMenu.tsx +++ b/assets/admin/components/inventory/InventoryActionsMenu.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; +import React, { useLayoutEffect, useRef, useState } from 'react'; import { EllipsisHorizontalCircleIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/24/outline'; +import Portal from '../ui/Portal'; import type { InventoryItem } from '../../hooks/useInventory'; interface Props { @@ -8,13 +9,39 @@ interface Props { onDelete: (item: InventoryItem) => void; } -/** «عملیات» trigger + dropdown — mirrors the tauri InventoryActionsPopover. */ +const MENU_W = 160; + +/** + * «عملیات» trigger + dropdown — mirrors the tauri InventoryActionsPopover. + * Rendered through a Portal with fixed positioning so it is never clipped by the + * table container's `overflow: hidden`. + */ export default function InventoryActionsMenu({ item, onEdit, onDelete }: Props) { const [open, setOpen] = useState(false); + const [pos, setPos] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); + const triggerRef = useRef(null); + + useLayoutEffect(() => { + if (!open || !triggerRef.current) return; + const place = () => { + const r = triggerRef.current!.getBoundingClientRect(); + // align the menu's right edge to the trigger's right edge (RTL-friendly) + const left = Math.max(8, Math.min(r.right - MENU_W, window.innerWidth - MENU_W - 8)); + setPos({ top: r.bottom + 4, left }); + }; + place(); + window.addEventListener('scroll', place, true); + window.addEventListener('resize', place); + return () => { + window.removeEventListener('scroll', place, true); + window.removeEventListener('resize', place); + }; + }, [open]); return ( -
+ <> {open && ( - <> -
setOpen(false)} - style={{ position: 'fixed', inset: 0, zIndex: 30 }} - /> + +
setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 60 }} />
@@ -68,8 +92,8 @@ export default function InventoryActionsMenu({ item, onEdit, onDelete }: Props) حذف
- + )} -
+ ); }