From c93d8e824ff6908bca439a39bcbe74d94d3ce00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Ben=C3=ADtez?= Date: Tue, 23 Jun 2026 10:01:57 -0300 Subject: [PATCH] =?UTF-8?q?fix(sprint-5/etapa-4h-2):=20observar=20prop=20o?= =?UTF-8?q?pen=20en=20vez=20de=20onOpenChange=20=E2=80=94=20fix=20anterior?= =?UTF-8?q?=20no=20se=20disparaba?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El fix anterior (1aa1486) interceptaba onOpenChange de SheetPrimitive.Root, pero ese callback solo se dispara cuando RADIX decide cerrar internamente (Escape, overlay, botón X). Cuando el padre cierra el Sheet cambiando el prop `open` directamente —que es lo que hace ProfileSheet.handleSave(): llama onOpenChange(false), que es setProfileOpen(false) del padre AppHeader— Radix nunca invoca su propio onOpenChange. El wrapper nunca se ejecutaba para este caso, exactamente el que reportó el usuario en producción (body queda con pointer-events:none stuck tras guardar perfil). Fix: useEffect que observa el prop `open` en sí, cubre ambos canales de cierre (interno de Radix y externo del padre) sin depender de qué disparó el cambio. Co-Authored-By: Claude Sonnet 4.6 --- src/components/ui/sheet.tsx | 43 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index 383c3f3..5bab4f2 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -6,27 +6,28 @@ import { cn } from '@/lib/utils' // Bug conocido de Radix/react-remove-scroll: si un re-render externo coincide con la // animación de cierre, el cleanup que restaura `pointer-events` del body no se ejecuta -// — toda la página queda no interactiva hasta refrescar manualmente. El defer de 350ms -// en algunos callers (ej. ProfileSheet) mitiga UNA causa, pero no es estructural: este -// fallback corrige el estado real del DOM sin asumir cuál fue la causa de la falla. -// 400ms > duración de la animación de salida (300ms, ver sheetVariants). -function Sheet({ onOpenChange, ...props }: React.ComponentPropsWithoutRef) { - return ( - { - onOpenChange?.(open) - if (!open) { - window.setTimeout(() => { - const stillHasOpenDialog = document.querySelector('[role="dialog"][data-state="open"]') - if (!stillHasOpenDialog && document.body.style.pointerEvents === 'none') { - document.body.style.pointerEvents = '' - } - }, 400) - } - }} - {...props} - /> - ) +// — toda la página queda no interactiva hasta refrescar manualmente. +// +// `onOpenChange` de Radix NO es un canal confiable para detectar esto: solo se dispara +// cuando RADIX decide cerrar internamente (Escape, click en overlay, botón X). Cuando el +// padre cierra el Sheet cambiando el prop `open` directamente (ej. ProfileSheet.handleSave +// llama onOpenChange(false), que es setProfileOpen(false) del padre), Radix nunca invoca +// su propio onOpenChange — por eso un wrapper de ese callback no detecta este caso. +// Observar el prop `open` en sí cubre ambos canales de cierre. +function Sheet({ open, ...props }: React.ComponentPropsWithoutRef) { + React.useEffect(() => { + if (open) return + // 400ms > duración de la animación de salida (300ms, ver sheetVariants). + const timeoutId = window.setTimeout(() => { + const stillHasOpenDialog = document.querySelector('[role="dialog"][data-state="open"]') + if (!stillHasOpenDialog && document.body.style.pointerEvents === 'none') { + document.body.style.pointerEvents = '' + } + }, 400) + return () => window.clearTimeout(timeoutId) + }, [open]) + + return } const SheetTrigger = SheetPrimitive.Trigger const SheetClose = SheetPrimitive.Close