8.9 KiB
Patrones de UI
Propósito: explicar cómo implementar pantallas concretas. Este documento, junto con Arquitectura frontend y UI system, forma la fuente de verdad (SSOT) de frontend/UI.
Los componentes aquí citados se definen en ui-system.md; este documento explica cómo componerlos.
1. Listados
<RdxPageFrame>
<RdxPageContainer width="full">
<RdxPageContent>
<RdxPageHeader
title={title}
description={description}
actions={<RdxPageActions primary={primaryAction} />}
/>
{refreshError}
<DataTable topContent={toolbarAndFilters} {...tableProps} />
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
El error inicial bloqueante conserva el mismo shell:
<RdxPageFrame>
<RdxPageContainer width="content">
<RdxPageContent>
<PageErrorState />
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
- El CTA de creación permanece durante loading, empty y errores de refresh; puede desaparecer ante error inicial bloqueante.
- Búsqueda, filtros y controles viven en
DataTable.topContent. - TanStack Query conserva los datos remotos y la paginación server-side.
- Búsqueda, filtros, sorting y paginación reflejan su estado relevante en
searchParamsy reinician página cuando corresponda. - No se calculan contadores globales a partir de una página parcial.
- El click de fila recibe la fila; la página/controller usa explícitamente
row.id. - Una vista de composición de dominio puede encapsular el shell si permanece pura.
- Los listados no reconstruyen con HTML local las responsabilidades de Frame, Container o Content. Search, filtros, tabs, tabla y paginación son contenido funcional de
RdxPageContent; el scroll interno de una tabla o grid es legítimo y no sustituye el scroll de página.
El controller de página concentra URL, filtros, consulta, estados y navegación. La Page define columnas y conecta callbacks; no reproduce reglas de dominio.
2. Create / Update
Estructura canónica: providers → RdxPageFrame (header/content/footer) → un único RdxPageContainer → un único RdxPageContent; los dialogs son hermanos del frame.
<RdxPageFrame
header={<RdxEditorHeader />}
footer={<RdxEditorActionBar />}
>
<RdxPageContainer width="content">
<RdxPageContent>
{pageError ? <ErrorAlert /> : null}
<Editor />
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
Los errores inline no definen su propio spacing estructural. Participan del spacing estándar de RdxPageContent igual que el resto de hijos principales.
Footer y aside son opcionales. content es adecuado para formularios focalizados; full para editores documentales amplios. Create y Update deben compartir geometría cuando solo cambian defaults, submit o navegación.
Loading y error bloqueante usan también RdxPageFrame y RdxPageContainer; no reconstruyen el viewport.
El error bloqueante conserva la misma jerarquía: RdxPageFrame → RdxPageContainer → RdxPageContent → PageErrorState.
3. Detail
Un detalle normal usa RdxPageFrame, RdxEditorHeader cuando es fullscreen y la jerarquía RdxPageContainer → RdxPageContent; errores, alertas, secciones semánticas y contenido funcional viven dentro de RdxPageContent.
Un detalle documental especializado puede usar:
RdxPageFrame content overflow-hidden
├── document pane → scroll interno
└── inspector → scroll interno
Solo se usa esta excepción cuando los panes necesitan permanecer visibles simultáneamente. Su scroll interno es parte del workspace documental, no el scroll estructural de la Page. No se crea un layout genérico de detalle si no existe repetición real.
4. Settings
Settings posee un layout funcional bajo un único frame:
RdxPageFrame
└── SettingsLayout
├── header
├── navigation
└── Outlet
Las páginas hijas no añaden otro RdxPageFrame cuando el padre ya controla shell y scroll. El layout de Settings se conserva porque aporta navegación y composición funcional.
5. Dialogs
Usar Dialog para una tarea breve y focalizada que debe volver al contexto actual. Usar AlertDialog para confirmaciones, especialmente destructivas. Una edición prolongada o documental necesita una ruta propia.
Orden recomendado:
DialogContent
├── DialogHeader: título y descripción
├── campo de identidad, si existe
├── secciones agrupadas por intención
└── DialogFooter: cancelar y enviar
- La cabecera y el footer se separan visualmente del formulario.
- Los bloques usan
sectionyaria-labelledbycuando tienen título. - Campos equivalentes pueden usar dos columnas desde
sm; en móvil se apilan. - El botón submit puede apuntar al formulario mediante
formsi el footer está fuera de<form>. - Durante submit se deshabilitan controles y acciones de cierre que puedan interrumpir la operación.
- El controller posee defaults, validación, mutation, reset, feedback y navegación posterior.
- Todo texto visible usa i18n en
esyen.
6. Acciones de fila
RdxRowActions recibe acciones ya resueltas:
- los listados usan
RdxRowActionspara mantener una presentación uniforme; - las frecuentes van en
primaryActionscomo botones de icono con tooltip; no se usan botones de texto en la columna de acciones; - las secundarias y destructivas van en
overflowActions, accesibles desde el menú de más acciones; - orden,
disabled,disabledReason, permisos y callbacks pertenecen al consumidor; - una acción deshabilitada puede seguir visible para explicar por qué no está disponible.
No se trasladan entidades, navegación, dialogs ni permisos a rdx-ui.
7. Loading, error y empty
| Estado | Patrón |
|---|---|
| Loading inicial de tabla | DataTable loading dentro del shell estable |
| Loading de editor | skeleton con el mismo frame/container que success |
| Error inicial bloqueante | PageErrorState dentro de frame y container |
| Error de refresh | ErrorAlert inline; mantiene datos y controles |
| Empty | mensaje/estado vacío en el stack del contenido |
No se crean sistemas alternativos de errores o skeletons cuando los componentes existentes cubren el caso.
8. Formularios
- React Hook Form y
FormProviderpertenecen al consumidor. RdxFormGridorganiza únicamente campos.- Errores de campo permanecen junto al control; errores de operación usan
ErrorAlert. - Dirty state, bloqueo de navegación y submit pertenecen al controller/provider correspondiente.
- La acción principal comunica loading y evita doble submit.
- Una única superficie editable es preferible a cards anidadas.
9. Main + aside
El aside es opcional y contiene metadata, resumen o contexto secundario. En desktop acompaña al main y en pantallas menores se apila debajo.
No usar el aside para equilibrar visualmente columnas, trasladar totales o fragmentar arbitrariamente el flujo del formulario. Tablas y line editors mantienen su layout especializado dentro del main.
10. Responsive
- Validar móvil, tablet y desktop.
- Evitar widths fijos en campos y controles localizados.
- Mantener acciones utilizables cuando se apilan.
- Confirmar
min-w-0, truncado intencional y ausencia de overflow horizontal. - En layouts especializados, comprobar pane sizes y scroll independiente.
11. Accesibilidad
- Controles nativos o primitives accesibles; nunca
divclicables. - Labels asociadas mediante
htmlFor/id. - Icon buttons con
aria-labely tooltips cuando corresponda. - Activación por teclado en filas y acciones.
- Foco visible, orden DOM lógico y errores anunciables.
- El color no sustituye texto, icono o estado semántico.
12. Testing
Usar tests de comportamiento con el runner vigente del workspace y Testing Library cuando existe interacción DOM. No probar detalles internos del controller ni clases visuales incidentales.
Cobertura mínima según patrón:
- listado: CTA, search, filtros, paginación, estados y navegación de fila;
- editor: header, contenido, acciones, dirty/submit y loading/error;
- dialog: título, campos, condicionales, cancelar/enviar y mutation;
- UI pública: contrato observable, accesibilidad y responsabilidades estructurales.
Las traducciones productivas deben probarse sin sustituirlas por un diccionario que oculte claves ausentes.
13. Checklist
- La ruta usa el shell real declarado en
handle.layout. - Existe un único
RdxPageFramepropietario del scroll. - Header editorial o fullscreen elegido por responsabilidad.
- Width y padding pertenecen a
RdxPageContainer. - El aside es funcional y opcional.
- No hay viewport o scroll principal manual en la Page.
- Loading, error, empty y success conservan estructura coherente.
- Acciones, permisos y navegación permanecen en el consumidor.
- Responsive, teclado, foco, i18n y light/dark están validados.
- Implementación, documentación, UI Kit y tests siguen sincronizados.