fix(ui): reemplaza panel-debajo-del-listado por DetailPopup de tamaño fijo

El detalle de un item de listado (actividad, departamento, usuario, proyecto nuevo,
catalogo) ya no se apila debajo ni al costado del listado: ahora abre en un popup que
cubre la ventana, deja el listado inactivo detras, nunca se cierra con clic afuera (solo
con el boton Cerrar o una accion explicita del formulario) y mantiene un alto FIJO por
instancia para que cambiar de pestaña interna (Info/Subtareas/Dependencias/Comentarios/
Documentos) no reajuste su tamaño.

De paso corrige un bug real de stacking: ContentDialog no usaba portal a document.body,
así que un dialogo de confirmacion abierto desde dentro de un DetailPopup quedaba oculto
detras de este. Ahora ambos son portales con una jerarquia de z-index explicita
(Flyout 100 < DetailPopup 200 < ContentDialog 250 < Toast 300).

En el detalle de actividad, Subtareas y Dependencias pasan de estar apiladas dentro de la
pestaña Info a ser cada una su propia pestaña.

Regla documentada en project-md-templates/UI-UX-WINDOWS.md (componente DetailPopup) para
que aplique a futuros proyectos de escritorio Windows del grupo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-09-03 11:21:07 -05:00
parent 1d22adc42e
commit 1e6e7fecdd
10 changed files with 224 additions and 142 deletions
+8 -3
View File
@@ -1,3 +1,4 @@
import { createPortal } from 'react-dom';
import { useUi } from '../context/ui-context.jsx';
import { Button } from './Button.jsx';
@@ -17,8 +18,11 @@ export function ContentDialogHost() {
if (!action.keepOpen) closeDialog();
}
return (
<div className="dialog-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) closeDialog(); }}>
// Regla dura: NUNCA se cierra con clic afuera (a diferencia de Flyout) — solo con un boton.
// Portal a document.body (igual que Flyout/DetailPopup): garantiza que quede SIEMPRE por
// encima de cualquier DetailPopup abierto, sin depender del punto del arbol donde se monta.
return createPortal(
<div className="dialog-overlay">
<div className="dialog-surface">
<div className="dialog-stripe" style={{ background: `rgb(var(--${STRIPE_COLOR[tone]}))` }} />
<div className="dialog-body">
@@ -33,6 +37,7 @@ export function ContentDialogHost() {
))}
</div>
</div>
</div>
</div>,
document.body
);
}
+25
View File
@@ -0,0 +1,25 @@
import { createPortal } from 'react-dom';
import { X } from 'lucide-react';
// Popup grande para ver/editar el detalle de un elemento de un listado (reemplaza el patron
// "panel debajo del listado"). Reglas duras:
// 1. NUNCA se cierra con clic afuera del popup — solo con el boton "Cerrar" de su header o una
// accion explicita del formulario (ej. Guardar/Cancelar).
// 2. Alto FIJO (no se ajusta al contenido): moverse entre pestañas/secciones internas del popup
// (ej. Info/Comentarios/Documentos) nunca debe cambiar su tamaño — solo el contenido interno
// hace scroll. `height` es fijo por instancia; ajustar via prop segun cuanto contenido espera.
export function DetailPopup({ open, title, onClose, children, width = 720, height = 640 }) {
if (!open) return null;
return createPortal(
<div className="detail-popup-overlay">
<div className="detail-popup" style={{ maxWidth: width, height }}>
<div className="detail-popup-header">
<div className="detail-popup-title">{title}</div>
<button className="icon-btn" onClick={onClose}><X /></button>
</div>
<div className="detail-popup-body">{children}</div>
</div>
</div>,
document.body
);
}