# انیمیشن اسلاید صفحه جزئیات سکشن روی لیست سکشن‌ها (مطابق حسینیه‌اپ) ## خلاصه تحقیق **حسینیه‌اپ** (مرجع): پنل مداح URL-driven است (`?panel=provider:id`) و صفحه لیست هرگز unmount نمی‌شود؛ پنل با CSS خالص (بدون کتابخانه) با `translateX(±100%) → 0` و زمان‌بندی **0.28s / cubic-bezier(0.32, 0.72, 0, 1)** اسلاید می‌شود. جهت با `side` فیزیکی انتخاب می‌شود: اپ RTL → پنل از **چپ** می‌آید (قرینه سایدبار راست). الگوی `PanelSlot` محتوای پنل را تا پایان انیمیشن خروج mount نگه می‌دارد. **اپ مریج**: Next.js 16.2.10 App Router، بدون هیچ کتابخانه انیمیشن. تمام مسیرهای بستن صفحه جزئیات از نوع `router.replace(questionsListHref)` هستند (۳ دکمه هدر، دکمه خروج با flush، هاردور بک) و دکمه close حالت تست به‌صورت پیش‌فرض `router.back()` می‌زند — یعنی طراحی باید مثل حسینیه «URL-driven با retention» باشد تا همه مسیرها خودکار انیمیت شوند، بدون دستکاری تک‌تک call-siteها. **معماری انتخابی** (طبق انتخاب شما): Parallel Route `@modal` + Intercepting Route `(.)[slug]` + هاست retention — الگوی رسمی modal مستندات همین نسخه Next (`node_modules/next/dist/docs/.../parallel-routes.md` و `intercepting-routes.md`، هر دو verify شد). ## ساختار فایل‌ها ### ۱. فایل‌های جدید (تماماً additive) ``` src/app/[lang]/questions-list/ ├── layout.tsx ← رندر {children} + {modal} (server) └── @modal/ ├── layout.tsx ← wrapper نازک → SectionOverlayHost ├── default.tsx ← return null (الگوی رسمی؛ جلوگیری از 404 در hard-load) └── (.)[slug]/ └── page.tsx ← export { default } from "@/app/questions-list/[slug]/page" ``` ``` src/components/Componentes/section-overlay-host.tsx ← "use client" — قلب مکانیزم ``` ### ۲. `SectionOverlayHost` (ترجمه‌ی PanelSlot حسینیه به Next App Router) - در `@modal/layout.ts` رندر می‌شود؛ چون layout اسلات در ناوبری‌های soft زنده می‌ماند، state آن پایدار است. - **State machine**: `phase: 'enter' | 'open' | 'closing' | 'closed'` - **باز شدن**: فرزند اسلات (صفحه intercept شده) mount می‌شود → `SectionOverlayContext` که host ارائه می‌دهد با `markActive()` از داخل `QuestionDetailClient` صدا زده می‌شود → host پنل را با کلاس off-screen رندر کرده و با `requestAnimationFrame` کلاس `open` اضافه می‌کند → CSS transition اسلاید ورود. - **بسته شدن (هر مسیری: replace / back / هاردور)**: children اسلات به `default` (null) تغییر می‌کند → host عنصر قبلی را در state نگه می‌دارد (retention) و کلاس `closing` می‌دهد → ۲۹۰ms بعد unmount واقعی. دقیقاً همان `activeDescriptor`/`mounted` در PanelSlot. - **RTL**: از `useI18n().locale` + `localeDirections` → `data-dir` روی پنل. **LTR از راست، RTL از چپ** (قرینه حسینیه که در RTL از چپ می‌آید). - **قفل اسکرول**: کلاس `section-overlay-open` روی `body` (قرینه الگوی موجود `body.dropdown-open .app-shell` در globals.css خط ۲۳۲). - **سایزینگ**: `position: fixed; inset-inline: 0; top/bottom: 0; margin-inline: auto` + `w-full sm:w-[375px]` + `padding-inline: 17px` + `padding-bottom: var(--safe-bottom)` — دقیقاً قرینه `.app-shell` (body فلکس و وسط‌چین است، globals.css خط ۱۵۴) تا `main` با `-mx-[17px]` داخل پنل مثل قبل رفتار کند. ### ۳. تغییر در `question-detail-client.tsx` (حداقلی، ~۶ خط) ```tsx const overlay = useSectionOverlay(); // خارج از overlay → undefined → no-op useEffect(() => overlay?.markActive(), [overlay]); ``` تست‌های موجود (`question-detail-client.test.tsx`) مستقیم رندر می‌کنند و context ندارند → بدون تغییر رفتار. ### ۴. CSS در `globals.css` ```css .section-overlay { transform: translateX(100%); /* LTR: ورود از راست */ transition: transform 280ms cubic-bezier(0.32, 0.72, 0, 1); will-change: transform; } [dir="rtl"] .section-overlay { transform: translateX(-100%); } /* RTL: ورود از چپ */ .section-overlay[data-open="true"], .section-overlay[data-open="closing"] { transform: translateX(0); } /* closing = همان حالت 0 که با برداشتن data-open="true" به سمت ابتدایی برمی‌گردد */ body.section-overlay-open .app-shell { overflow-y: hidden; } @media (prefers-reduced-motion: reduce) { .section-overlay { transition: none; } } ``` + سایه لبه داخلی پنل مثل `shadow-[-18px_0_50px_rgba(0,0,0,.45)]` حسینیه (جهت سایه هم با RTL برعکس). منحنی و مدت زمان عیناً از `anim-sheet-left` حسینیه (`0.28s cubic-bezier(0.32, 0.72, 0, 1)`). ## رفتار نهایی - کلیک روی سکشن در `/en/questions-list` → URL به `/en/questions-list/personal_identity` تغییر می‌کند، لیست زیر پنل می‌ماند (اسکرول حفظ می‌شود)، پنل از **راست** (در `/fa` از **چپ**) با همان انیمیشن حسینیه اسلاید می‌شود. - هر مسیر بستن (دکمه close با flush پاسخ‌ها، back مرورگر، هاردور بک اندروید، اتمام سابمیت) → پنل با همان انیمیشن به همان سمت جمع می‌شود و لیست از زیر پیدا می‌شود. - بارگذاری مستقیم/refresh روی URL جزئیات → صفحه کامل فعلی بدون انیمیشن (مطابق رفتار حسینیه در hard-load). - متن جدیدی اضافه نمی‌شود → بدون تغییر فایل‌های locale. ## نکات اجرا و ریسک - Dev با `--webpack` اجرا می‌شود؛ interception با webpack پشتیبانی می‌شود. - `router.prefetch` از لیست (که الان هم هست) نسخه intercept شده را prefetch می‌کند → ورود آنی. - اگر build روی static-params اسلات گیر کرد (بعید، همه force-dynamic هستند): `export const dynamic = "force-dynamic"` به صفحه intercept شده اضافه می‌شود. - کد مرده `info-progress-card.tsx` (لینک non-localized بدون استفاده) دست نمی‌خورد. ## تست و راستی‌آزمایی 1. `npm run test` (vitest) — تست‌های موجود نباید بشکنند. 2. `npm run lint` (biome). 3. دستی با dev server روی پورت 3001: - `/en/questions-list` → کلیک سکشن: اسلاید از راست؛ `/fa/questions-list` → اسلاید از چپ. - بستن با دکمه close (flush)، back مرورگر، و شبیه‌سازی هاردور بک — همه با انیمیشن خروج. - refresh مستقیم روی `/en/questions-list/personal_identity` → صفحه کامل. - حفظ اسکرول لیست پس از بستن؛ قفل اسکرول پشت پنل هنگام باز بودن.له