From 02c34bac8e44ffe73dc31cd09da3fc5a8ac9cdb1 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Thu, 2 Jul 2026 11:48:34 +0330 Subject: [PATCH] fix(modal): render Modal with React Portal to center it on the screen fix(calendar): add type="button" to all buttons in PersianCalendar to prevent form submission --- .../prompt/fix-admin-modal-and-calendar.md | 151 ++++++++++++++++++ assets/admin/components/ui/Modal.tsx | 8 +- .../admin/components/ui/PersianCalendar.tsx | 5 + 3 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 .claude/prompt/fix-admin-modal-and-calendar.md diff --git a/.claude/prompt/fix-admin-modal-and-calendar.md b/.claude/prompt/fix-admin-modal-and-calendar.md new file mode 100644 index 00000000..0dd24ce5 --- /dev/null +++ b/.claude/prompt/fix-admin-modal-and-calendar.md @@ -0,0 +1,151 @@ +# رفع دو باگ Modal و تقویم شمسی در پنل ادمین + +## پروژه + +`clinicpro` (Admin SPA — React 19 داخل Symfony/Encore). + +## زمینه + +در پنل ادمین دو باگ UI مستقل وجود دارد که هر دو در `/admin/profile` و سایر صفحات دیده می‌شوند: + +1. **مودال وسط صفحه باز نمی‌شود و به پایین صفحه می‌چسبد** — در همهٔ صفحاتی که از کامپوننت مشترک `Modal` استفاده می‌کنند. +2. **تقویم شمسی هنگام کار با دکمه‌های ناوبری بسته می‌شود** — مثلاً در فیلد «تاریخ شروع فعالیت» وقتی وارد نمای انتخاب سال می‌شوی و روی دکمه‌های `<` / `>` (تغییر بازهٔ سال) کلیک می‌کنی، به‌جای جابه‌جایی بازه، کل فرم submit و مودال بسته می‌شود. + +## مشکل / هدف + +### باگ ۱ — علت +کامپوننت `Modal` محتوای خود را **inline در همان‌جای درخت DOM** رندر می‌کند (بدون Portal). کلاس `.overlay` از `position: fixed; inset: 0; display: grid; place-items: center` استفاده می‌کند که باید نسبت به viewport وسط‌چین کند؛ اما وقتی یکی از عناصر والد یک containing-block برای `position: fixed` بسازد (هر عنصری با `transform` / `filter` / `perspective` / `contain: paint` / `will-change`)، مبنای `fixed` از viewport به آن والد تغییر می‌کند و overlay داخل جعبهٔ بلندِ آن والد کشیده می‌شود؛ در نتیجه `place-items: center` مودال را در وسط آن جعبهٔ بلند (که پایین‌تر از دید کاربر است) قرار می‌دهد، نه وسط صفحه. راه‌حل قطعی و مستقل از اینکه کدام والد مقصر است: رندر Modal با **React Portal روی `document.body`**. + +### باگ ۲ — علت +در `PersianCalendar.tsx` هیچ‌کدام از ` + +
{children}
+ {footer &&
{footer}
} + + + ); +} +``` + +### `PersianCalendar.tsx` (buttonها بدون `type`) — نمونه‌ها + +```tsx +// دکمه‌های ناوبری هدر + +// ... + + +// سلول روز + + +// سلول ماه + + +// سلول سال + +``` + +`mini-btn` بستن در `Modal` هم بدون `type` است و باید اصلاح شود (اگر مودالی داخل فرم قرار گیرد). + +## وظایف + +### ۱. رندر `Modal` با Portal روی `document.body` + +`createPortal` را از `react-dom` وارد کن و کل markup مودال را داخل آن بپیچ: + +```tsx +import React, { useEffect } from 'react'; +import { createPortal } from 'react-dom'; +import { XMarkIcon } from '@heroicons/react/24/outline'; + +// ... داخل کامپوننت، بعد از `if (!open) return null;` + return createPortal( +
+
e.stopPropagation()}> + {/* ... بدون تغییر ... */} +
+
, + document.body + ); +``` + +نکته‌ها: +- Portal تضمین می‌کند overlay فرزندِ مستقیم `body` باشد، پس `position: fixed` همیشه نسبت به viewport محاسبه می‌شود و باگ چسبیدن به پایین در همهٔ صفحات رفع می‌شود. +- منطق Escape و `onClick` overlay و `stopPropagation` مودال بدون تغییر بماند. +- دکمهٔ بستن `mini-btn` را `type="button"` کن تا اگر مودالی داخل یک `` قرار گرفت، submit ناخواسته رخ ندهد. + +### ۲. افزودن `type="button"` به همهٔ ` +``` + +این کار از submit ناخواستهٔ فرمِ دربرگیرنده جلوگیری می‌کند و تقویم هنگام کار با `<` / `>` و انتخاب سال/ماه باز می‌ماند؛ فقط انتخاب «روز» (که `onChange` + `onClose` را صدا می‌زند) آن را می‌بندد. + +### ۳. (اختیاری، اگر جای دیگری هم مشکل مشابه بود) بررسی سریر سایر تقویم‌ها + +`PersianDatePicker.tsx` (والدِ `PersianCalendar`) دکمهٔ باز/بستن‌اش `
` است نه `
{children}
{footer &&
{footer}
} - + , + document.body ); } diff --git a/assets/admin/components/ui/PersianCalendar.tsx b/assets/admin/components/ui/PersianCalendar.tsx index 1c5da859..5f1ee125 100644 --- a/assets/admin/components/ui/PersianCalendar.tsx +++ b/assets/admin/components/ui/PersianCalendar.tsx @@ -127,6 +127,7 @@ export default function PersianCalendar({ value, onChange, onClose, enableYearPi {/* Header */}