Detail Drawer Pattern — Kanon
DetayAdmin paneldeki “detay drawer” component’lari (EmailDetailDrawer, SmsDetailDrawer vb.) bu pattern’i takip eder. Yeni drawer eklerken once buraya bak.
Kapsam:
components/admin/communications/*DetailDrawer.tsx. Genel-amaccomponents/admin/design-system/Drawer.tsxprimitive ayri konu — bu doc detay verilerini gosteren drawer’lar icindir.
1. Parent: key={id} ile fresh mount
Bölüm başlığı “1. Parent: key={id} ile fresh mount”LogTable component’inde drawer mount’i id’ye bagli key ile yapilir:
{openId !== null && ( <DetailDrawer key={openId} id={openId} onClose={() => setOpenId(null)} />)}Neden: React key degisince component’i otomatik unmount + remount
yapar. Tum useState’ler default’a doner. Drawer icinde useEffect+
setState ile manuel reset yazmaya gerek yoktur — cascading render
riski ortadan kalkar.
Anti-pattern (eskiden boyleydi):
// ❌ kaldirildiuseEffect(() => { setData(null); setTab('default'); setPayload(null); fetch(...).then(setData);}, [id]);Sync setState reset’leri React Compiler’in set-state-in-effect
kuralina takiliyordu. Parent key pattern bu reset’leri gereksizlestirir.
2. Fetch: race-safe + async setData
Bölüm başlığı “2. Fetch: race-safe + async setData”useEffect’te sadece async fetch + cancel guard kalir:
useEffect(() => { let cancelled = false; void fetch(`/api/admin/communications/emails/${id}`) .then(r => r.json()) .then(j => { if (!cancelled) setData(j.log); }); return () => { cancelled = true; };}, [id]);Neden cancel guard: key={id} remount’undan ayri olarak, ayni
drawer icinde id parent state’inden A→B cabuk degisirse, A’nin
response’u B’nin uzerine yazabilir. cancelled flag bunu onler.
Async setData (.then(setData)) cascading render degil — React
Compiler kurali sync setState’i hedefler.
3. Payload reveal: shared hook
Bölüm başlığı “3. Payload reveal: shared hook”Encrypted payload reveal + auto-mask + clipboard mantigi tek hook’ta toplanmistir:
import { usePayloadReveal } from '@/hooks/use-payload-reveal';
const { payload, secondsLeft, onReveal, onCopy } = usePayloadReveal( `/api/admin/communications/emails/${id}/payload`,);Endpoint kontrati:
POST {endpoint}body{ source: 'view' | 'copy' }- Response
{ payload: unknown }
Hook 30s auto-mask timer’ini barindirir; component cleanup’ta timer
clear edilir. Sabit: REVEAL_TIMEOUT_MS (drawer-shared.ts’de).
4. Stiller: shared module
Bölüm başlığı “4. Stiller: shared module”backdropStyle + drawerStyle components/admin/communications/drawer-shared.ts
icindedir. Yeni drawer ayni stili import eder. Variant gerekiyorsa
shared’a yeni export ekle (ornek: drawerStyleNarrow), inline yazma.
5. Footer pattern (mevcut konvansiyon)
Bölüm başlığı “5. Footer pattern (mevcut konvansiyon)”Mevcut iki drawer’da footer:
↻ Resendbutonu →ResendDialogacarDeletebutonu → nativeconfirm()→ DELETE endpoint →onClose()
Yeni drawer farkli aksiyon barindirabilir; ama Resend + Delete varsa ayni butonlari kullan.
6. Test sozlesmesi
Bölüm başlığı “6. Test sozlesmesi”Pure-function (REVEAL_TIMEOUT_MS, sabit) testleri node:test ile
calistirilabilir. Hook’un timing davranisi component-level test
gerektirir — su an codebase’de jsdom + RTL kurulu degil, manuel
browser smoke yeterli.
7. Z-index ladder (proje genelinde)
Bölüm başlığı “7. Z-index ladder (proje genelinde)”Drawer/Modal layer’larin tek satirda anlasilir hiyerarsisi. Yeni overlay eklerken bu skaladan secim yap, kafadan rakam kullanma.
| zIndex | Katman | Ornek |
|---|---|---|
| 20 | Sticky header, icerik dropdown | AdminPanelContent, LeadStatusSelect |
| 40 | Mobile sidebar overlay | AdminSidebar, CustomerSidebar |
| 50 | Drawer + Modal (standart) | primitive Drawer/Modal, EmailDetailDrawer, SmsDetailDrawer, CommandPalette |
| 60 | Drawer’dan acilan dialog | ResendDialog (Email/Sms drawer ustunde durur) |
| 200 | Fullscreen editor | ImageEditor, sidebar logout confirm |
Yanlis pattern (eskiden boyleydi):
- Email/Sms drawer
zIndex: 900/901— primitive Drawer (Tailwindz-50) ile farkli katmanda, ResendDialogzIndex: 1000patch ile bunu asmaya calisiyordu. Cozum: hepsini ayni skalaya getir.
Yeni drawer yazarken:
- Drawer’in kendi backdrop+panel:
zIndex: 50(drawer-shared.ts’den import) - Drawer’dan acilan dialog:
zIndex: 60(ResendDialogornegi) - Daha ustte gostermek istiyorsan → ladder’a yeni satir ekle, dokumante et
8. Anti-pattern referans tablosu
Bölüm başlığı “8. Anti-pattern referans tablosu”| Yapma | Yap |
|---|---|
Sync setState reset useEffect icinde | Parent key={id} ile fresh mount |
Drawer icinde inline backdropStyle | import { backdropStyle } from './drawer-shared' |
| Payload reveal duplicate | usePayloadReveal(endpoint) hook |
| Cancel guard’siz fetch | let cancelled = false; return () => { cancelled = true; } |
| Timer cleanup eksik | Hook icinde clearInterval + clearTimeout cleanup |
Referanslar
Bölüm başlığı “Referanslar”components/admin/communications/EmailDetailDrawer.tsx— kanon implementasyoncomponents/admin/communications/SmsDetailDrawer.tsx— kanon implementasyoncomponents/admin/communications/drawer-shared.ts— sabit + stylehooks/use-payload-reveal.ts— payload reveal hookdocs/admin-design-system/STYLE.md— genel design system