Refactor form dialogs and UI components for improved consistency and usability
- Updated DeleteBrandDialog and DeleteCatalogDefinitionDialog to use FormBody and FormActions for better structure. - Replaced DialogFooter with FormActions in various dialogs to standardize button layouts. - Introduced FormSectionTitle in multiple components for consistent section headings. - Adjusted grid layouts to use responsive design with @min-[560px] for better adaptability. - Created SoftwareResourceCreateDialog and SoftwareInstallationEditorDialog for better separation of concerns in software installation management. - Enhanced FormActions to support cancelDisabled prop for better control over button states. - Refined FormSectionTitle to allow for additional HTML elements like "legend" and "span". - Added EntityTableColumnsDialog for managing table column visibility in a more user-friendly manner.
This commit is contained in:
parent
ccb52fb534
commit
a0f02ced6e
@ -88,9 +88,7 @@ React, TanStack Query, Tailwind y shadcn/ui (Base UI, estilo Vega). Los componen
|
||||
Estado remoto en TanStack Query; claves centralizadas e invalidación explícita después de mutaciones.
|
||||
No duplicar estado remoto ni usar efectos para derivar valores calculables durante render.
|
||||
Mantener accesibilidad y estados de carga, error, vacío y acciones deshabilitadas.
|
||||
Los títulos de grupos en todos los diálogos siguen el estilo de «Datos de la plantilla»: `text-xs font-medium uppercase tracking-wide text-muted-foreground`. Reutilizar `FormSectionTitle` de @repo/rdx-ui, también dentro de `FormSection`, conservando el nivel semántico del encabezado. La variante del contenedor no cambia este estilo. Los títulos principales del diálogo y los nombres editables de secciones conservan su tratamiento propio.
|
||||
Todos los diálogos de selección de tipo `PickerList` utilizan `FormDialog layout="picker"`: altura estable adaptada a la ventana, independiente del número de resultados. La tabla ocupa el espacio disponible con `EntityTable layout="fill"`; búsqueda, filtros, paginación y acciones quedan estables, y los resultados tienen desplazamiento interno con espacio reservado para la barra. En ventanas pequeñas se permite desplazamiento adicional del cuerpo para mantener accesibles todos los controles.
|
||||
En los diálogos de selección de tipo `PickerList`, la columna del checkbox o radio se sitúa siempre al principio, antes de las columnas de datos, con identificador `selection`, cabecera solo visible para lectores de pantalla y anchura ajustada al control, próxima a la columna del nombre, sin ordenación ni posibilidad de ocultarla. No se trata como columna `actions` ni se ancla a la derecha. Pulsar cualquier punto no interactivo de una fila disponible debe activar/desactivar su checkbox, o seleccionar su radio en modo único. Reutilizar `onRowClick` de la tabla común, respetar las filas deshabilitadas y conservar la navegación por teclado del control. Pulsar directamente el checkbox, radio u otro control debe ejecutar solo su interacción propia, sin duplicar el cambio por propagación.
|
||||
Todos los diálogos nuevos o modificados siguen el [patrón de diseño de cuadros de diálogo](docs/patron-de-dialogos.md), fuente normativa para títulos uniformes, separación entre grupos, distribución responsive, alturas iguales por fila, secciones plegables, filas compactas, ordenación accesible y selección múltiple. Reutilizar los componentes de @repo/rdx-ui indicados en el patrón. En todos los `PickerList`, mantener altura estable, columna `selection` compacta al principio y selección al pulsar zonas no interactivas de la fila. Aplicar cada pauta según el tipo de contenido; conservar los campos y restricciones propios de cada entidad.
|
||||
Separar componentes por responsabilidades reales, sin fragmentar JSX trivial.
|
||||
Antes de crear o modificar pantallas, reconocer activamente patrones visuales y de interacción comunes entre ellas, así como componentes repetidos dentro de una misma pantalla. Aplicar soluciones coherentes a todos los casos equivalentes, conservando las necesidades propias de cada entidad.
|
||||
Reutilizar y extender la biblioteca visual existente antes de duplicar estructura, estilos o comportamiento. Extraer componentes compartidos cuando haya repetición real: cabeceras, listados, formularios modales, grupos de campos, selectores con alta, estados y ayuda contextual.
|
||||
@ -224,6 +222,8 @@ Estas reglas complementan las convenciones y documentos de arquitectura existent
|
||||
|
||||
#### Diálogos, modales y ventanas emergentes
|
||||
|
||||
El diseño visual y de interacción sigue [el patrón de diálogos](docs/patron-de-dialogos.md); las siguientes reglas definen la organización de sus componentes.
|
||||
|
||||
- Páginas y listados no deben implementar directamente el contenido de ventanas modales funcionales. Los diálogos de alta, edición, confirmación, selección o detalle son componentes independientes.
|
||||
- Toda ventana modal, cuadro de diálogo o formulario modal con contenido y responsabilidad propios debe extraerse a un componente React independiente y vivir en su propio fichero `.tsx`.
|
||||
- Esta regla se aplica especialmente a componentes construidos con primitivas de shadcn/ui como `Dialog`, `AlertDialog`, `Sheet`, `Drawer` o componentes propios como `FormDialog`, pero no depende del nombre concreto utilizado.
|
||||
|
||||
@ -9,6 +9,10 @@ consumen mediante `@repo/rdx-ui`. Los componentes que conocen contratos, textos,
|
||||
reglas de Metis viven en el módulo o aplicación que los utiliza; nunca en ninguno de los dos
|
||||
paquetes genéricos.
|
||||
|
||||
El [patrón de diseño de cuadros de diálogo](patron-de-dialogos.md) es la fuente normativa
|
||||
para componer grupos, columnas responsive, filas compactas y selectores. Esta guía describe
|
||||
los componentes que lo implementan y sus contratos.
|
||||
|
||||
## Componentes actuales
|
||||
|
||||
- Button: volver a comprobar la conexión.
|
||||
@ -495,7 +499,7 @@ URLs de vista previa se liberan al cambiar de archivo o desmontar el componente.
|
||||
|
||||
El alta y la edición de plantillas de activos y recursos agrupan los datos generales con `FormSection` e incorporan campos desde las cabeceras de sus grupos. `TemplateSectionsEditor` permite secciones apiladas o una cuadrícula responsive mediante `sectionLayout="stack" | "responsive"`, con una cabecera numerada sobre fondo diferenciado que contiene el input del nombre, el asa de arrastre y las acciones de orden y eliminación. El cuerpo conserva la descripción y un área de campos separada por un borde. `TemplateSectionEditor` encapsula cada sección con `Collapsible`: todas empiezan abiertas y un botón de cabecera permite plegarlas con ratón o teclado. El nombre, el asa y las acciones siguen visibles al plegar. El contenido permanece montado para conservar los controles; si la validación nativa detecta un control inválido oculto, la sección se abre antes de que el navegador lo enfoque. La apertura se conserva por identidad al reordenar y no se guarda como parte de la plantilla. Los editores de activos y recursos usan la cuadrícula responsive: una columna hasta 1.000 px de ancho disponible y dos a partir de ese umbral, con separación de 16 px y tarjetas de igual altura en cada fila, determinada por la sección más alta. El orden recorre cada fila de izquierda a derecha y se conserva en el DOM, la numeración, el teclado y el arrastre; la altura de cada fila se recalcula al plegar o desplegar sus secciones, manteniendo ambas tarjetas con la misma altura. El bloque «Sin sección» permanece debajo, a ancho completo.
|
||||
|
||||
`TemplateSectionFields` muestra un título traducido y el número de campos. Cada campo tiene fondo propio y separa su nombre y acciones de la configuración de obligatoriedad y asignación, cuyo selector conserva una etiqueta visible. Los campos se distribuyen en dos columnas a partir de 700 px de su propio grupo y en una para anchos menores; cada grupo establece su contenedor para evitar columnas demasiado estrechas cuando las secciones se muestran en paralelo. El bloque «Sin sección» se diferencia con borde discontinuo; las secciones vacías conservan un mensaje explícito. Se reutilizan los colores semánticos del tema para mantener el contraste en modo claro y oscuro.
|
||||
`TemplateSectionFields` muestra un título traducido y el número de campos. Cada campo tiene fondo propio. En la presentación `cards`, separa su nombre y acciones de la configuración de obligatoriedad y asignación, cuyo selector conserva una etiqueta visible; los editores de plantillas de activos y recursos utilizan la presentación `compact` descrita más abajo. Los campos se distribuyen en dos columnas a partir de 700 px de su propio grupo y en una para anchos menores; cada grupo establece su contenedor para evitar columnas demasiado estrechas cuando las secciones se muestran en paralelo. El bloque «Sin sección» se diferencia con borde discontinuo; las secciones vacías conservan un mensaje explícito. Se reutilizan los colores semánticos del tema para mantener el contraste en modo claro y oscuro.
|
||||
|
||||
### Orden mediante arrastre y teclado
|
||||
|
||||
@ -537,10 +541,28 @@ El alta y la edición de plantillas de activos y recursos reutilizan `TemplateMe
|
||||
|
||||
## Títulos de grupos en diálogos
|
||||
|
||||
Todos los diálogos con grupos utilizan el estilo de «Datos de la plantilla»: texto pequeño, peso medio, mayúsculas, espaciado entre letras y color atenuado (`text-xs font-medium uppercase tracking-wide text-muted-foreground`). `FormSectionTitle` centraliza esta presentación y recibe `children`, `id` opcional y `as="h3" | "h4" | "h5"` para conservar la jerarquía semántica. El consumidor aporta el texto traducido; las mayúsculas se aplican mediante CSS.
|
||||
Todos los diálogos con grupos utilizan el estilo de «Datos de la plantilla»: texto pequeño, seminegrita, mayúsculas, espaciado entre letras y color atenuado (`text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-4`). `FormSectionTitle` centraliza esta presentación y recibe `children`, `id` opcional y `as="h3" | "h4" | "h5" | "legend" | "span"` para conservar la semántica del encabezado, leyenda o título dentro de un `summary`. El consumidor aporta el texto traducido; las mayúsculas se aplican mediante CSS. La variante `span` conserva la tipografía y deja el espaciado al `summary` que la contiene, sin margen inferior propio.
|
||||
|
||||
`FormSection`, `TemplateSectionsEditor` y `TemplateSectionFields` comparten este título, con independencia de sus bordes, acciones o contenido. La regla también se aplica a grupos de consulta, como los puestos del historial de perfiles. Los títulos principales del diálogo y los nombres editables de secciones mantienen su presentación propia.
|
||||
|
||||
### Campos compactos en plantillas de activos y recursos
|
||||
|
||||
`TemplateSectionsEditor fieldLayout="compact"` presenta cada campo en una fila: asa de arrastre, switch de obligatoriedad, nombre flexible, flechas arriba/abajo y papelera. El nombre puede ocupar varias líneas en pantallas estrechas. El switch conserva una etiqueta accesible con «Obligatorio» y el nombre del campo; su texto visual y el selector de sección se omiten. Las notas de campos inactivos siguen disponibles para lectores de pantalla. Se mantienen el arrastre, las flechas por teclado, los límites de orden y los estados deshabilitados. Ambos editores utilizan esta presentación compacta.
|
||||
|
||||
## Ajustes comunes tras la revisión de diálogos
|
||||
|
||||
`FormBody` establece su propio contenedor de consultas de tamaño: las cuadrículas de formularios
|
||||
y subcontroles utilizan `@min-[560px]` para decidir sus dos columnas según el ancho real del
|
||||
modal. `FieldSections` aplica la misma regla a los campos y estira los grupos de cada fila
|
||||
a la altura de su vecino más alto. Los títulos de consulta, grupos de permisos y secciones
|
||||
desplegables reutilizan `FormSectionTitle`, conservando el estilo vigente del componente.
|
||||
|
||||
`FormActions` admite `cancelDisabled` para conservar el bloqueo de cancelación durante una
|
||||
confirmación pendiente, además de `leadingActions` para acciones opcionales a la izquierda.
|
||||
Las confirmaciones de marcas y definiciones de catálogo usan `FormBody` y `FormActions` para
|
||||
mantener su pie fuera del área desplazable. La selección de compañía del dashboard también
|
||||
sitúa Continuar en ese pie y lo deshabilita mientras falta una selección o una consulta correcta.
|
||||
|
||||
`SoftwareInstallationEditorDialog`, `SoftwareResourceCreateDialog` y `EntityTableColumnsDialog`
|
||||
encapsulan sus interacciones en ficheros independientes. Sus consumidores conservan apertura,
|
||||
selección, datos y callbacks; los formularios y controles mantienen sus contratos anteriores.
|
||||
|
||||
@ -1,177 +1,171 @@
|
||||
# Inventario de interacciones modales
|
||||
|
||||
Revisión de 2026-10-08. Se inspeccionaron las primitivas y wrappers importados, sus consumidores,
|
||||
los bloques condicionales y las implementaciones manuales posibles: Dialog, AlertDialog, Sheet,
|
||||
Drawer, FormDialog, portales, elementos dialog, aria-modal y roles dialog/alertdialog.
|
||||
La búsqueda no dependió del nombre de los componentes.
|
||||
Revisión de código de 2026-10-15 según el [patrón de diseño de diálogos](patron-de-dialogos.md).
|
||||
Se buscaron constructores `FormDialog`, `ConfirmDeleteDialog` y `FieldPickerDialog`, además de
|
||||
primitivas `Dialog`, `AlertDialog`, `Sheet`, `Drawer`, portales y diálogos manuales. Se revisaron
|
||||
los formularios, controles compartidos y contenido de consulta que reciben estos constructores.
|
||||
|
||||
El código propio contiene **81 componentes que construyen una interacción modal**. Todos
|
||||
usan FormDialog de @repo/rdx-ui y tienen un componente principal por fichero. Un componente
|
||||
puede representar variantes de alta, edición o copia, y catálogos con distintos tipos; el número
|
||||
no es un recuento de todos los estados posibles de las ventanas. No se encontraron otras
|
||||
implementaciones propias con AlertDialog, Sheet, Drawer ni diálogos HTML/manuales.
|
||||
El código propio contiene **81 componentes modales en módulos** y **3 constructores
|
||||
compartidos en @repo/rdx-ui**. El recuento identifica componentes, no cada variante de alta,
|
||||
edición, copia o estado. Las dos variantes de CatalogFieldPickerDialog son adaptadores del
|
||||
mismo selector genérico. El código vendor de `packages/shadcn-ui` queda fuera de esta revisión
|
||||
y no se modifica.
|
||||
|
||||
Actualización de 2026-10-09: se añade DeleteBrandDialog para confirmar la eliminación de
|
||||
marcas personalizadas; el inventario pasa a 82 componentes. Se ha comprobado esta operación
|
||||
en navegador con API simulada: Cancelar/Escape, errores, bloqueo durante el envío, éxito,
|
||||
español/inglés y anchura móvil. Esta comprobación no amplía la revisión al resto de modales.
|
||||
## Resultado de la revisión
|
||||
|
||||
La implementación vendor de packages/shadcn-ui queda fuera de la refactorización según AGENTS.md.
|
||||
Sus primitivas y el diálogo de configuración de tablas se consultan y consumen, no se duplican
|
||||
ni reorganizan. Los popovers de selectores y menús de acciones no se contabilizan como formularios
|
||||
o ventanas modales funcionales.
|
||||
- Los diálogos utilizan la estructura común de cabecera, cuerpo desplazable y pie. Las
|
||||
confirmaciones de marcas y catálogo se han adaptado al mismo pie fijo, conservando el
|
||||
bloqueo durante el envío y la presentación de errores.
|
||||
- Los formularios y sus subcontroles calculan las columnas según su contenedor. Los títulos
|
||||
de grupos de consulta, permisos y secciones comparten el estilo de Datos de la plantilla.
|
||||
- Las secciones de plantillas conservan dos columnas cuando hay espacio, igual altura por
|
||||
fila, plegado, campos compactos, ordenación accesible e incorporación de campos por grupo.
|
||||
- AssetPickerDialog y los dos CatalogFieldPickerDialog mantienen altura estable, selección
|
||||
compacta al principio, selección desde la fila y confirmación provisional. Los diálogos
|
||||
de configuración de columnas son ajustes, no selectores de entidades PickerList.
|
||||
- Se extrajeron los diálogos de edición de instalaciones de software, alta de su recurso y
|
||||
configuración de columnas. Los padres mantienen apertura y datos, sin definir el modal.
|
||||
- Las variantes de selección de compañía del dashboard colocan Continuar en el pie común.
|
||||
Los formularios sencillos, historiales y ayudas mantienen su tamaño según contenido; las
|
||||
fechas de revisión y nombres de registros conservan su tipografía de datos, no de grupos.
|
||||
- Se conservan los pasos de alta de recursos ya existentes: no se ha añadido ningún asistente
|
||||
para resolver únicamente espacio. Tampoco se añaden cuadrículas a formularios sencillos.
|
||||
|
||||
## Responsabilidades y consumidores
|
||||
## Componentes revisados
|
||||
|
||||
Las páginas y listados conservan apertura, selección y callbacks. Los componentes enumerados
|
||||
encapsulan título, disposición, anchura, contenido y cierre. Los formularios existentes mantienen
|
||||
sus schemas, borradores, mutaciones, ayuda y errores. EndRelationshipDialog posee además la
|
||||
mutación de confirmación y sus estados pendientes y de error.
|
||||
Las rutas siguen la [estructura por entidades y operaciones](estructura-web-por-entidades-y-operaciones.md).
|
||||
La columna Composición indica el constructor utilizado; las variantes heredan sus reglas comunes.
|
||||
|
||||
CompanyEditorDialog, PersonEditorDialog, DepartmentEditorDialog y SiteEditorDialog se reutilizan
|
||||
el patrón entre catálogo y selector. Los callbacks de selección permanecen en el consumidor que
|
||||
posee el borrador. Las altas anidadas conservan sus restricciones existentes.
|
||||
### assets (23)
|
||||
|
||||
Se integró el contenido exclusivo de los antiguos SupportHistory, ApplicationHistory,
|
||||
CertificateHistory y PrinterHistory en sus respectivos componentes de diálogo, tras comprobar
|
||||
sus referencias. No se eliminaron historiales ni acciones. Los archivos anteriores se pueden
|
||||
recuperar desde Git.
|
||||
| Componente | Composición |
|
||||
| --- | --- |
|
||||
| [EndRelationshipDialog](../modules/assets/src/web/asset-relationships/end/ui/end-relationship-dialog.tsx) | `FormDialog` |
|
||||
| [RelationshipEditorDialog](../modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx) | `FormDialog` |
|
||||
| [WorkstationAssetCreateDialog](../modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx) | `FormDialog` |
|
||||
| [AssetDetailDialog](../modules/assets/src/web/assets/detail/ui/asset-detail-dialog.tsx) | `FormDialog` |
|
||||
| [AssetPickerDialog](../modules/assets/src/web/assets/select/ui/asset-picker-dialog.tsx) | `FormDialog` · altura estable |
|
||||
| [AssetEditorDialog](../modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx) | `FormDialog` |
|
||||
| [DeleteAssetDialog](../modules/assets/src/web/assets/shared/ui/delete-asset-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||
| [DeleteBrandDialog](../modules/assets/src/web/brands/delete/ui/delete-brand-dialog.tsx) | `FormDialog` |
|
||||
| [BrandEditorDialog](../modules/assets/src/web/brands/shared/ui/brand-editor-dialog.tsx) | `FormDialog` |
|
||||
| [DeleteCatalogDefinitionDialog](../modules/assets/src/web/catalog/delete/ui/delete-catalog-definition-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogDefinitionDialog](../modules/assets/src/web/catalog/detail/ui/catalog-definition-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogEditorDialog](../modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogFieldCreateDialog](../modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogFieldPickerDialog](../modules/assets/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx) | `FieldPickerDialog` · altura estable |
|
||||
| [CatalogRelationCreateDialog](../modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogTemplateCreateDialog](../modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx) | `FormDialog` |
|
||||
| [DeleteRecordDialog](../modules/assets/src/web/shared/ui/delete-record-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||
| [SupportHistoryDialog](../modules/assets/src/web/support-connections/history/ui/support-history-dialog.tsx) | `FormDialog` |
|
||||
| [SupportConnectionEditorDialog](../modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx) | `FormDialog` |
|
||||
| [SupportResourceCreateDialog](../modules/assets/src/web/support-connections/shared/ui/support-resource-create-dialog.tsx) | `FormDialog` |
|
||||
| [WorkstationHistoryDialog](../modules/assets/src/web/workstations/history/ui/workstation-history-dialog.tsx) | `FormDialog` |
|
||||
| [WorkstationEditorDialog](../modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx) | `FormDialog` |
|
||||
| [WorkstationLifecycleDialog](../modules/assets/src/web/workstations/shared/ui/workstation-lifecycle-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||
|
||||
## Componentes por área
|
||||
### companies (49)
|
||||
|
||||
Las rutas reflejan la [estructura por entidades y operaciones](estructura-web-por-entidades-y-operaciones.md).
|
||||
| Componente | Composición |
|
||||
| --- | --- |
|
||||
| [AccessQuickEditor](../modules/companies/src/web/access/create/ui/access-quick-editor.tsx) | `FormDialog` |
|
||||
| [QuickCreateDialog](../modules/companies/src/web/access/create/ui/quick-create-dialog.tsx) | `FormDialog` |
|
||||
| [AccessHelpDialog](../modules/companies/src/web/access/help/ui/access-help-dialog.tsx) | `FormDialog` |
|
||||
| [AccessHistory](../modules/companies/src/web/access/history/ui/access-history.tsx) | `FormDialog` |
|
||||
| [AccessEditorDialog](../modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx) | `FormDialog` |
|
||||
| [ApplicationQuickEditor](../modules/companies/src/web/application-configurations/create/ui/application-quick-editor.tsx) | `FormDialog` |
|
||||
| [ApplicationHistoryDialog](../modules/companies/src/web/application-configurations/history/ui/application-history-dialog.tsx) | `FormDialog` |
|
||||
| [ApplicationConfigurationEditorDialog](../modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||
| [AssignmentHistoryDialog](../modules/companies/src/web/assignments/history/ui/assignment-history-dialog.tsx) | `FormDialog` |
|
||||
| [AssignmentEditorDialog](../modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx) | `FormDialog` |
|
||||
| [CertificateQuickEditor](../modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx) | `FormDialog` |
|
||||
| [CertificateHistoryDialog](../modules/companies/src/web/certificate-configurations/history/ui/certificate-history-dialog.tsx) | `FormDialog` |
|
||||
| [CertificateConfigurationEditorDialog](../modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||
| [InventoryQuickCreateDialog](../modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx) | `FormDialog` |
|
||||
| [CompanyEditorDialog](../modules/companies/src/web/companies/shared/ui/company-editor-dialog.tsx) | `FormDialog` |
|
||||
| [DashboardQuickCreate](../modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx) | `FormDialog` |
|
||||
| [DashboardDetailsDialog](../modules/companies/src/web/dashboard/detail/ui/dashboard-details-dialog.tsx) | `FormDialog` |
|
||||
| [DepartmentEditorDialog](../modules/companies/src/web/departments/shared/ui/department-editor-dialog.tsx) | `FormDialog` |
|
||||
| [FolderQuickEditor](../modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx) | `FormDialog` |
|
||||
| [FolderMappingHistoryDialog](../modules/companies/src/web/folder-mappings/history/ui/folder-mapping-history-dialog.tsx) | `FormDialog` |
|
||||
| [FolderMappingEditorDialog](../modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx) | `FormDialog` |
|
||||
| [MailQuickEditor](../modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx) | `FormDialog` |
|
||||
| [MailConfigurationHistoryDialog](../modules/companies/src/web/mail-configurations/history/ui/mail-configuration-history-dialog.tsx) | `FormDialog` |
|
||||
| [MailConfigurationEditorDialog](../modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||
| [MailboxUseEditorDialog](../modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx) | `FormDialog` |
|
||||
| [MailboxCreateDialog](../modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx) | `FormDialog` |
|
||||
| [PersonEditorDialog](../modules/companies/src/web/people/shared/ui/person-editor-dialog.tsx) | `FormDialog` |
|
||||
| [PrinterProfileCreateDialog](../modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx) | `FormDialog` |
|
||||
| [PrinterHistoryDialog](../modules/companies/src/web/printer-configurations/history/ui/printer-history-dialog.tsx) | `FormDialog` |
|
||||
| [PrinterConfigurationEditorDialog](../modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx) | `FormDialog` |
|
||||
| [ProcessTemplateEditorDialog](../modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx) | `FormDialog` |
|
||||
| [ProcessQuickCreateDialog](../modules/companies/src/web/processes/create/ui/process-quick-create-dialog.tsx) | `FormDialog` |
|
||||
| [ProcessStartDialog](../modules/companies/src/web/processes/start/ui/process-start-dialog.tsx) | `FormDialog` |
|
||||
| [ProcessEditDialog](../modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx) | `FormDialog` |
|
||||
| [RemoteQuickEditor](../modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx) | `FormDialog` |
|
||||
| [RemoteConnectionHistoryDialog](../modules/companies/src/web/remote-connections/history/ui/remote-connection-history-dialog.tsx) | `FormDialog` |
|
||||
| [RemoteConnectionEditorDialog](../modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx) | `FormDialog` |
|
||||
| [ConfigurationQuickEditor](../modules/companies/src/web/shared/ui/configuration-quick-editor.tsx) | `FormDialog` |
|
||||
| [DeleteRecordDialog](../modules/companies/src/web/shared/ui/delete-record-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||
| [SiteEditorDialog](../modules/companies/src/web/sites/shared/ui/site-editor-dialog.tsx) | `FormDialog` |
|
||||
| [SoftwareResourceCreateDialog](../modules/companies/src/web/software-installations/create/ui/software-resource-create-dialog.tsx) | `FormDialog` |
|
||||
| [SoftwareHistoryDialog](../modules/companies/src/web/software-installations/history/ui/software-history-dialog.tsx) | `FormDialog` |
|
||||
| [SoftwareInstallationEditorDialog](../modules/companies/src/web/software-installations/shared/ui/software-installation-editor-dialog.tsx) | `FormDialog` |
|
||||
| [TeleworkAuthorizationHistoryDialog](../modules/companies/src/web/telework-authorizations/history/ui/telework-authorization-history-dialog.tsx) | `FormDialog` |
|
||||
| [TeleworkAuthorizationEditorDialog](../modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx) | `FormDialog` |
|
||||
| [WorkProfileHistoryDialog](../modules/companies/src/web/work-profiles/history/ui/work-profile-history-dialog.tsx) | `FormDialog` |
|
||||
| [WorkProfileEditorDialog](../modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx) | `FormDialog` |
|
||||
| [WorkProfileLifecycleDialog](../modules/companies/src/web/work-profiles/shared/ui/work-profile-lifecycle-dialog.tsx) | `ConfirmDeleteDialog` |
|
||||
| [WorkspaceSettingsDialog](../modules/companies/src/web/workspace/settings/ui/workspace-settings-dialog.tsx) | `FormDialog` |
|
||||
|
||||
### modules/assets/src/web
|
||||
### identity (2)
|
||||
|
||||
- [AssetDetailDialog](../modules/assets/src/web/assets/detail/ui/asset-detail-dialog.tsx)
|
||||
- [AssetEditorDialog](../modules/assets/src/web/assets/shared/ui/asset-editor-dialog.tsx)
|
||||
- [BrandEditorDialog](../modules/assets/src/web/brands/shared/ui/brand-editor-dialog.tsx)
|
||||
- [DeleteBrandDialog](../modules/assets/src/web/brands/delete/ui/delete-brand-dialog.tsx)
|
||||
- [CatalogDefinitionDialog](../modules/assets/src/web/catalog/detail/ui/catalog-definition-dialog.tsx)
|
||||
- [CatalogEditorDialog](../modules/assets/src/web/catalog/shared/ui/catalog-editor-dialog.tsx)
|
||||
- [CatalogFieldCreateDialog](../modules/assets/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx)
|
||||
- [CatalogFieldPickerDialog](../modules/assets/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx): selección múltiple provisional para añadir campos a una plantilla; reutiliza la tabla común y confirma el lote antes de cambiar el borrador.
|
||||
- [CatalogRelationCreateDialog](../modules/assets/src/web/catalog-relations/create/ui/catalog-relation-create-dialog.tsx)
|
||||
- [CatalogTemplateCreateDialog](../modules/assets/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx)
|
||||
- [EndRelationshipDialog](../modules/assets/src/web/asset-relationships/end/ui/end-relationship-dialog.tsx)
|
||||
- [RelationshipEditorDialog](../modules/assets/src/web/asset-relationships/shared/ui/relationship-editor-dialog.tsx)
|
||||
- [SupportConnectionEditorDialog](../modules/assets/src/web/support-connections/shared/ui/support-connection-editor-dialog.tsx)
|
||||
- [SupportHistoryDialog](../modules/assets/src/web/support-connections/history/ui/support-history-dialog.tsx)
|
||||
- [WorkstationAssetCreateDialog](../modules/assets/src/web/assets/create/ui/workstation-asset-create-dialog.tsx)
|
||||
- [WorkstationEditorDialog](../modules/assets/src/web/workstations/shared/ui/workstation-editor-dialog.tsx)
|
||||
- [WorkstationHistoryDialog](../modules/assets/src/web/workstations/history/ui/workstation-history-dialog.tsx)
|
||||
| Componente | Composición |
|
||||
| --- | --- |
|
||||
| [AccountPasswordDialog](../modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx) | `FormDialog` |
|
||||
| [UserEditorDialog](../modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx) | `FormDialog` |
|
||||
|
||||
### modules/companies/src/web/access
|
||||
### resources (7)
|
||||
|
||||
- [AccessEditorDialog](../modules/companies/src/web/access/shared/ui/access-editor-dialog.tsx)
|
||||
- [AccessHelpDialog](../modules/companies/src/web/access/help/ui/access-help-dialog.tsx)
|
||||
- [AccessHistory](../modules/companies/src/web/access/history/ui/access-history.tsx)
|
||||
- [AccessQuickEditor](../modules/companies/src/web/access/create/ui/access-quick-editor.tsx)
|
||||
- [QuickCreateDialog](../modules/companies/src/web/access/create/ui/quick-create-dialog.tsx)
|
||||
| Componente | Composición |
|
||||
| --- | --- |
|
||||
| [CatalogDefinitionDialog](../modules/resources/src/web/catalog/detail/ui/catalog-definition-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogEditorDialog](../modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogFieldCreateDialog](../modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx) | `FormDialog` |
|
||||
| [CatalogFieldPickerDialog](../modules/resources/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx) | `FieldPickerDialog` · altura estable |
|
||||
| [CatalogTemplateCreateDialog](../modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx) | `FormDialog` |
|
||||
| [ResourceDetailDialog](../modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx) | `FormDialog` |
|
||||
| [ResourceEditorDialog](../modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx) | `FormDialog` |
|
||||
|
||||
### modules/companies/src/web/application-configurations
|
||||
### rdx-ui (3)
|
||||
|
||||
- [ApplicationConfigurationEditorDialog](../modules/companies/src/web/application-configurations/shared/ui/application-configuration-editor-dialog.tsx)
|
||||
- [ApplicationHistoryDialog](../modules/companies/src/web/application-configurations/history/ui/application-history-dialog.tsx)
|
||||
- [ApplicationQuickEditor](../modules/companies/src/web/application-configurations/create/ui/application-quick-editor.tsx)
|
||||
| Componente | Composición |
|
||||
| --- | --- |
|
||||
| [ConfirmDeleteDialog](../packages/rdx-ui/src/components/confirm-delete-dialog.tsx) | `FormDialog` |
|
||||
| [EntityTableColumnsDialog](../packages/rdx-ui/src/components/entity-table-columns-dialog.tsx) | `FormDialog` |
|
||||
| [FieldPickerDialog](../packages/rdx-ui/src/components/field-picker-dialog.tsx) | `FormDialog` · altura estable |
|
||||
|
||||
### modules/companies/src/web/certificate-configurations
|
||||
## Validación y límites
|
||||
|
||||
- [CertificateConfigurationEditorDialog](../modules/companies/src/web/certificate-configurations/shared/ui/certificate-configuration-editor-dialog.tsx)
|
||||
- [CertificateHistoryDialog](../modules/companies/src/web/certificate-configurations/history/ui/certificate-history-dialog.tsx)
|
||||
- [CertificateQuickEditor](../modules/companies/src/web/certificate-configurations/create/ui/certificate-quick-editor.tsx)
|
||||
La revisión cubre código y render estático mediante las pruebas existentes. Las comprobaciones
|
||||
interactivas de apertura, Escape, restauración de foco, arrastre, envío, errores y altura visual
|
||||
en español/inglés y anchos estrecho/amplio quedan pendientes de una sesión de navegador.
|
||||
No se dispone de una herramienta de navegador en esta sesión; los análisis de código, tipos,
|
||||
formato y pruebas no sustituyen esa revisión visual.
|
||||
|
||||
### Compañías, organización, dashboard y workspace de companies
|
||||
Resultados de los comandos ejecutados:
|
||||
|
||||
- [AssignmentEditorDialog](../modules/companies/src/web/assignments/shared/ui/assignment-editor-dialog.tsx)
|
||||
- [AssignmentHistoryDialog](../modules/companies/src/web/assignments/history/ui/assignment-history-dialog.tsx)
|
||||
- [CompanyEditorDialog](../modules/companies/src/web/companies/shared/ui/company-editor-dialog.tsx)
|
||||
- [ConfigurationQuickEditor](../modules/companies/src/web/shared/ui/configuration-quick-editor.tsx)
|
||||
- [DashboardDetailsDialog](../modules/companies/src/web/dashboard/detail/ui/dashboard-details-dialog.tsx)
|
||||
- [DashboardQuickCreate](../modules/companies/src/web/dashboard/create/ui/dashboard-quick-create.tsx)
|
||||
- [DepartmentEditorDialog](../modules/companies/src/web/departments/shared/ui/department-editor-dialog.tsx)
|
||||
- [InventoryQuickCreateDialog](../modules/companies/src/web/companies/detail/ui/inventory-quick-create-dialog.tsx)
|
||||
- [PersonEditorDialog](../modules/companies/src/web/people/shared/ui/person-editor-dialog.tsx)
|
||||
- [SiteEditorDialog](../modules/companies/src/web/sites/shared/ui/site-editor-dialog.tsx)
|
||||
- [WorkspaceSettingsDialog](../modules/companies/src/web/workspace/settings/ui/workspace-settings-dialog.tsx)
|
||||
| Comprobación | Resultado |
|
||||
| --- | --- |
|
||||
| Typecheck dirigido a rdx-ui, assets, resources, companies e identity | Correcto |
|
||||
| Biome y ESLint dirigidos a los archivos de código modificados | Correctos |
|
||||
| `pnpm build` | Correcto; conserva el aviso de chunks grandes |
|
||||
| `pnpm test` | UI, activos, recursos, identidad, core y DDD correctos; empresas mantiene el fallo previo de `companies-page.test.tsx` |
|
||||
| `pnpm typecheck` | Mantiene TS2375 previo en `packages/shadcn-ui/src/components/calendar.tsx:165` |
|
||||
| `pnpm lint` / `pnpm check` | Mantienen respectivamente 14 y 155 errores previos ajenos al cambio |
|
||||
| `git diff --check` y enlaces locales de la documentación | Correctos |
|
||||
|
||||
### modules/companies/src/web/folder-mappings
|
||||
|
||||
- [FolderMappingEditorDialog](../modules/companies/src/web/folder-mappings/shared/ui/folder-mapping-editor-dialog.tsx)
|
||||
- [FolderMappingHistoryDialog](../modules/companies/src/web/folder-mappings/history/ui/folder-mapping-history-dialog.tsx)
|
||||
- [FolderQuickEditor](../modules/companies/src/web/folder-mappings/create/ui/folder-quick-editor.tsx)
|
||||
|
||||
|
||||
|
||||
### Buzones, usos y configuraciones de correo de companies
|
||||
|
||||
- [MailboxCreateDialog](../modules/companies/src/web/mailboxes/create/ui/mailbox-create-dialog.tsx)
|
||||
- [MailboxUseEditorDialog](../modules/companies/src/web/mailbox-uses/shared/ui/mailbox-use-editor-dialog.tsx)
|
||||
- [MailConfigurationEditorDialog](../modules/companies/src/web/mail-configurations/shared/ui/mail-configuration-editor-dialog.tsx)
|
||||
- [MailConfigurationHistoryDialog](../modules/companies/src/web/mail-configurations/history/ui/mail-configuration-history-dialog.tsx)
|
||||
- [MailQuickEditor](../modules/companies/src/web/mail-configurations/create/ui/mail-quick-editor.tsx)
|
||||
|
||||
### modules/companies/src/web/printer-configurations
|
||||
|
||||
- [PrinterConfigurationEditorDialog](../modules/companies/src/web/printer-configurations/shared/ui/printer-configuration-editor-dialog.tsx)
|
||||
- [PrinterHistoryDialog](../modules/companies/src/web/printer-configurations/history/ui/printer-history-dialog.tsx)
|
||||
- [PrinterProfileCreateDialog](../modules/companies/src/web/printer-configurations/create/ui/printer-profile-create-dialog.tsx)
|
||||
|
||||
### modules/companies/src/web/processes
|
||||
|
||||
- [ProcessEditDialog](../modules/companies/src/web/processes/update/ui/process-edit-dialog.tsx)
|
||||
- [ProcessQuickCreateDialog](../modules/companies/src/web/processes/create/ui/process-quick-create-dialog.tsx)
|
||||
- [ProcessStartDialog](../modules/companies/src/web/processes/start/ui/process-start-dialog.tsx)
|
||||
|
||||
### modules/companies/src/web/process-templates
|
||||
|
||||
- [ProcessTemplateEditorDialog](../modules/companies/src/web/process-templates/shared/ui/process-template-editor-dialog.tsx)
|
||||
|
||||
### modules/companies/src/web/work-profiles
|
||||
|
||||
- [WorkProfileEditorDialog](../modules/companies/src/web/work-profiles/shared/ui/work-profile-editor-dialog.tsx)
|
||||
- [WorkProfileHistoryDialog](../modules/companies/src/web/work-profiles/history/ui/work-profile-history-dialog.tsx)
|
||||
|
||||
### modules/companies/src/web/remote-connections
|
||||
|
||||
- [RemoteConnectionEditorDialog](../modules/companies/src/web/remote-connections/shared/ui/remote-connection-editor-dialog.tsx)
|
||||
- [RemoteConnectionHistoryDialog](../modules/companies/src/web/remote-connections/history/ui/remote-connection-history-dialog.tsx)
|
||||
- [RemoteQuickEditor](../modules/companies/src/web/remote-connections/create/ui/remote-quick-editor.tsx)
|
||||
|
||||
### modules/companies/src/web/telework-authorizations
|
||||
|
||||
- [TeleworkAuthorizationEditorDialog](../modules/companies/src/web/telework-authorizations/shared/ui/telework-authorization-editor-dialog.tsx)
|
||||
- [TeleworkAuthorizationHistoryDialog](../modules/companies/src/web/telework-authorizations/history/ui/telework-authorization-history-dialog.tsx)
|
||||
|
||||
|
||||
### modules/identity/src/web
|
||||
|
||||
- [AccountPasswordDialog](../modules/identity/src/web/passwords/change/ui/account-password-dialog.tsx)
|
||||
- [UserEditorDialog](../modules/identity/src/web/users/shared/ui/user-editor-dialog.tsx)
|
||||
- [UserManagementDialog](../modules/identity/src/web/users/list/ui/user-management-dialog.tsx)
|
||||
|
||||
### modules/resources/src/web
|
||||
|
||||
- [CatalogDefinitionDialog](../modules/resources/src/web/catalog/detail/ui/catalog-definition-dialog.tsx)
|
||||
- [CatalogEditorDialog](../modules/resources/src/web/catalog/shared/ui/catalog-editor-dialog.tsx)
|
||||
- [CatalogFieldCreateDialog](../modules/resources/src/web/catalog-fields/create/ui/catalog-field-create-dialog.tsx)
|
||||
- [CatalogFieldPickerDialog](../modules/resources/src/web/catalog-fields/select/ui/catalog-field-picker-dialog.tsx): adaptador del selector compartido para incorporar campos al destino y ofrecer alta rápida configurable.
|
||||
- [CatalogTemplateCreateDialog](../modules/resources/src/web/catalog-templates/create/ui/catalog-template-create-dialog.tsx)
|
||||
- [ResourceDetailDialog](../modules/resources/src/web/resources/detail/ui/resource-detail-dialog.tsx)
|
||||
- [ResourceEditorDialog](../modules/resources/src/web/resources/shared/ui/resource-editor-dialog.tsx)
|
||||
|
||||
## Comprobaciones y límites
|
||||
|
||||
Typecheck, lint, pruebas de los módulos y validaciones globales correctos. Las pruebas existentes
|
||||
comprueban traducciones es/en, listados sin formularios visibles, ayuda, restricciones de altas
|
||||
anidadas, estados y contratos. Se amplió la prueba del dashboard con datos no vacíos de los
|
||||
paneles extraídos.
|
||||
|
||||
No se ejecutó una sesión interactiva de navegador. Queda por comprobar manualmente apertura,
|
||||
Escape, restauración del foco, cancelación, envío y error en modales simples y anidados, en español,
|
||||
inglés y anchura móvil. El render estático y el lint no sustituyen esa comprobación.
|
||||
|
||||
Véase [revisión de buenas prácticas](revision-de-buenas-practicas.md) para el alcance y los resultados
|
||||
globales, y [componentes de interfaz](componentes-de-interfaz.md) para los patrones compartidos.
|
||||
|
||||
Los modales de tickets, intervenciones y sus plantillas están [archivados](funcionalidades-archivadas.md).
|
||||
El fallo de empresas se reprodujo ejecutando directamente el fichero: una prueba de persona
|
||||
espera el texto «Esta persona no tiene acceso a Metis Desk», ausente en el render actual.
|
||||
No se ha modificado esa prueba ni el contenido de la ayuda para ocultar el fallo.
|
||||
Los módulos de tickets, intervenciones y sus plantillas permanecen [archivados](funcionalidades-archivadas.md).
|
||||
|
||||
118
docs/patron-de-dialogos.md
Normal file
118
docs/patron-de-dialogos.md
Normal file
@ -0,0 +1,118 @@
|
||||
# Patrón de diseño de cuadros de diálogo
|
||||
|
||||
Fuente normativa para crear o modificar diálogos de Metis Desk. Recoge las decisiones
|
||||
aplicadas a los editores de plantillas de activos y recursos y a los selectores `PickerList`.
|
||||
La [guía de componentes](componentes-de-interfaz.md) describe sus APIs y consumidores;
|
||||
el [inventario de modales](inventario-de-modales.md) recoge las implementaciones existentes.
|
||||
|
||||
## Composición y reutilización
|
||||
|
||||
- Cada interacción modal con responsabilidad propia vive en un componente y fichero `.tsx`
|
||||
independientes. El padre decide su apertura y aporta datos y callbacks.
|
||||
- Reutilizar `FormDialog`, `FormBody` y `FormActions` de `@repo/rdx-ui`, apoyados en las
|
||||
primitivas de shadcn/ui y Base UI. Consultar los patrones existentes antes de crear otro.
|
||||
- Mantener los bloques visuales genéricos independientes de entidades, DTO, consultas y
|
||||
endpoints. El módulo aporta textos traducidos, controles, datos y acciones.
|
||||
- Aplicar el mismo patrón a alta, edición y copia y a entidades equivalentes. Conservar los
|
||||
campos y restricciones propios de cada entidad, como el uso de las plantillas de recursos.
|
||||
|
||||
## Grupos, títulos y separación
|
||||
|
||||
- Usar `FormSectionTitle` para los títulos de grupo: texto pequeño, seminegrita, mayúsculas,
|
||||
espaciado entre letras y color atenuado (`text-xs font-semibold uppercase tracking-wide
|
||||
text-muted-foreground mb-4`). Conservar el nivel semántico del encabezado; las mayúsculas
|
||||
pertenecen a la presentación CSS y los textos pasan por i18n.
|
||||
- El título principal del diálogo y los nombres editables de secciones tienen su propia
|
||||
función. El nombre editable de una sección va en su cabecera, junto a sus acciones.
|
||||
- Para los datos generales, preferir `FormSection variant="plain"`, sin borde ni relleno de
|
||||
tarjeta. Los grupos repetidos que necesitan identidad visual usan borde y fondo semánticos
|
||||
discretos. Evitar acumular marcos y rellenos en grupos anidados.
|
||||
- Mantener más separación entre grupos principales que entre controles relacionados. En el
|
||||
patrón de plantillas, `TemplateMetadataSection` deja 36 px antes del grupo de secciones
|
||||
(20 px del cuerpo y 16 px adicionales); las columnas y tarjetas se separan 16 px.
|
||||
- Situar las acciones locales en la cabecera del grupo al que afectan. Por ejemplo,
|
||||
«Añadir campo» se ofrece en «Campos» y lleva implícita la sección de destino.
|
||||
|
||||
## Distribución responsive y alturas
|
||||
|
||||
- Decidir las columnas por el ancho disponible del contenedor, mediante container queries.
|
||||
En espacios estrechos, apilar el contenido y permitir que nombres y acciones se adapten.
|
||||
- En formularios con nombre, icono y descripción, reutilizar `TemplateMetadataSection`:
|
||||
nombre e icono debajo a la izquierda, descripción a la derecha, en dos columnas iguales
|
||||
desde 560 px. Ambas columnas tienen igual altura y el área de descripción ocupa el alto
|
||||
restante. En una columna, el recorrido es nombre, icono y descripción.
|
||||
- Para secciones repetidas equivalentes, usar una columna con poco espacio y dos cuando sus
|
||||
controles quepan con comodidad. `TemplateSectionsEditor sectionLayout="responsive"`
|
||||
cambia a dos columnas desde 1.000 px. Las secciones de cada fila tienen la misma altura,
|
||||
determinada por la más alta, y se reajustan al plegar, desplegar o cambiar su contenido.
|
||||
- Mantener el orden de izquierda a derecha y de arriba abajo en el DOM, la numeración,
|
||||
el teclado y el arrastre. No introducir un orden visual diferente mediante CSS.
|
||||
- Cada grupo de campos responde a su propio ancho, también cuando su sección ocupa media
|
||||
fila. El bloque «Sin sección» permanece debajo de las secciones, a ancho completo.
|
||||
- No convertir formularios sencillos en cuadrículas de secciones ni imponerles altura fija.
|
||||
Los umbrales anteriores corresponden a los componentes citados; otros contenidos deben
|
||||
elegir su disposición según el espacio que necesitan sus controles.
|
||||
|
||||
## Secciones plegables y filas compactas
|
||||
|
||||
- Las secciones extensas o repetidas utilizan la primitiva `Collapsible` mediante
|
||||
`TemplateSectionEditor`. Empiezan abiertas y conservan el estado de apertura por identidad
|
||||
al reordenarse. El nombre, el agarre y las acciones permanecen en la cabecera.
|
||||
- Conservar los controles al plegar. Si la validación detecta un control inválido oculto,
|
||||
abrir la sección antes de enfocarlo. Plegar no modifica los datos.
|
||||
- En listas de configuración de campos, utilizar la presentación compacta
|
||||
`TemplateSectionsEditor fieldLayout="compact"`: agarre, switch de obligatoriedad, nombre,
|
||||
flechas de desplazamiento y papelera. Evitar una segunda fila de controles cuando esta
|
||||
configuración sea suficiente. Esta pauta corresponde a referencias de campos dentro de
|
||||
una plantilla; los formularios de edición de sus valores conservan sus controles propios.
|
||||
- Dar nombre accesible a los switches y botones de icono, incluyendo el campo o la sección.
|
||||
La obligatoriedad conserva su etiqueta accesible y ayuda al pasar el ratón.
|
||||
- Reutilizar `IconPicker` como desplegable compacto con icono y texto, incluida la opción
|
||||
sin icono. Evitar una fila permanente con todas las opciones de iconos.
|
||||
|
||||
## Ordenación y selección de campos
|
||||
|
||||
- Reutilizar `useDragReorder`, `ReorderHandle` y el indicador de destino. Mantener flechas
|
||||
explícitas y ordenación por teclado; el arrastre no es la única forma de cambiar el orden.
|
||||
- Las secciones se ordenan entre sí y los campos dentro de su sección. Conservar el foco
|
||||
y bloquear los movimientos imposibles o las acciones durante el guardado. Cancelar un
|
||||
arrastre no cambia los datos y guardar confirma el borrador del formulario.
|
||||
- Cuando se incorporen varias referencias de un catálogo, utilizar un selector múltiple
|
||||
en diálogo en lugar de un desplegable que obligue a añadirlas una a una. En plantillas,
|
||||
reutilizar `CatalogFieldPickerDialog` desde la cabecera de cada grupo de campos, sin un
|
||||
bloque independiente de incorporación tras los datos generales.
|
||||
- Confirmar la selección como lote, conservarla al buscar o paginar y descartar la selección
|
||||
provisional al cancelar. Respetar límites, disponibilidad y ausencia de duplicados.
|
||||
- El alta rápida opcional se ofrece a la izquierda del pie mediante `leadingActions`.
|
||||
Debe poder ocultarse o deshabilitarse desde el consumidor. Mantener el selector y su estado
|
||||
durante el alta anidada; crear el elemento no confirma por sí solo el lote seleccionado.
|
||||
|
||||
## Diálogos de selección PickerList
|
||||
|
||||
- Todos utilizan `FormDialog layout="picker"` y `EntityTable layout="fill"`: altura estable
|
||||
adaptada a la ventana, independiente de búsquedas, carga y número de resultados. La tabla
|
||||
ocupa el espacio restante y mantiene desplazamiento interno con espacio reservado para
|
||||
la barra; búsqueda, filtros, paginación y acciones permanecen estables.
|
||||
- Mantener el área de resultados incluso vacía y las cabeceras visibles durante el scroll.
|
||||
En ventanas pequeñas, permitir el desplazamiento adicional del cuerpo para acceder a
|
||||
todos los controles.
|
||||
- La columna `selection` es la primera, antes del nombre: anchura ajustada al checkbox o
|
||||
radio, poco espacio hasta el nombre y cabecera visualmente oculta con nombre accesible.
|
||||
No se ordena, no se oculta y no se ancla a la derecha como las acciones.
|
||||
- Pulsar una zona no interactiva de una fila disponible alterna su checkbox o selecciona
|
||||
su radio. Reutilizar `onRowClick` de la tabla; los controles internos ejecutan solo su
|
||||
propia acción, sin duplicar cambios. Respetar filas deshabilitadas y navegación por teclado.
|
||||
|
||||
## Cuerpo, pie y accesibilidad
|
||||
|
||||
- Elegir la anchura del diálogo según su contenido y respetar los márgenes de la ventana.
|
||||
Mantener cabecera y pie visibles y el cuerpo desplazable cuando el contenido lo requiera.
|
||||
- Los formularios conservan altura según contenido; la altura estable por resultados es
|
||||
la regla de los `PickerList`. No introducir pestañas ni pasos para resolver solo espacio.
|
||||
- Situar ayuda contextual general al final del cuerpo, inmediatamente antes de `FormActions`.
|
||||
Mantener errores junto al contexto que los origina y las acciones dentro del formulario.
|
||||
- Conservar foco, cancelación, cierre por teclado, estados de carga, error, vacío y envío.
|
||||
No cerrar ni limpiar el formulario antes de confirmar una operación correcta.
|
||||
- Validar los cambios de interacción con pruebas dirigidas y revisar visualmente los anchos
|
||||
estrecho y amplio, nombres largos, secciones de distinta altura, plegado y resultados vacíos
|
||||
cuando correspondan. Informar de las comprobaciones que no se hayan podido ejecutar.
|
||||
@ -1,4 +1,5 @@
|
||||
import { BrowserLink } from "@metis/core/client";
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -65,7 +66,7 @@ export function AssetRelationships(props: AssetRelationshipsProps) {
|
||||
return (
|
||||
<section aria-label={t("relations")} className="space-y-3">
|
||||
<header className="flex flex-wrap items-center justify-between gap-2">
|
||||
{!compact && <h3 className="font-medium">{t("relations")}</h3>}
|
||||
{!compact && <FormSectionTitle>{t("relations")}</FormSectionTitle>}
|
||||
<Button onClick={() => setAdding(true)} size="sm" variant="outline">
|
||||
{t("linkAsset")}
|
||||
</Button>
|
||||
|
||||
@ -2,6 +2,7 @@ import { BrowserLink } from "@metis/core/client";
|
||||
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
||||
import { DetailSection } from "@repo/rdx-ui/components/detail-section";
|
||||
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useAssetDetailController } from "../controllers";
|
||||
@ -28,7 +29,7 @@ export function AssetDetail(props: AssetDetailProps) {
|
||||
: String(value);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="@container space-y-5">
|
||||
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
|
||||
{!compact && (
|
||||
<DetailSection title={t("detailSections.identification")}>
|
||||
@ -72,9 +73,7 @@ export function AssetDetail(props: AssetDetailProps) {
|
||||
</DetailSection>
|
||||
<div>
|
||||
{!asset.specification.sections?.length && (
|
||||
<h3 className="py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{t("characteristics")}
|
||||
</h3>
|
||||
<FormSectionTitle>{t("characteristics")}</FormSectionTitle>
|
||||
)}
|
||||
<FieldSections
|
||||
items={asset.specification.fields}
|
||||
@ -105,9 +104,9 @@ export function AssetDetail(props: AssetDetailProps) {
|
||||
{children}
|
||||
{!compact && (
|
||||
<>
|
||||
<h3 className="font-medium">
|
||||
<FormSectionTitle>
|
||||
{t("history")} ({asset.history.length})
|
||||
</h3>
|
||||
</FormSectionTitle>
|
||||
<ol className="space-y-4">
|
||||
{[...asset.history].reverse().map((revision) => (
|
||||
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
||||
@ -125,7 +124,9 @@ export function AssetDetail(props: AssetDetailProps) {
|
||||
{t("site")}: {nameOf(revision.values.siteId, lookups.sites)}
|
||||
</p>
|
||||
<details>
|
||||
<summary className="cursor-pointer">{t("characteristics")}</summary>
|
||||
<summary className="cursor-pointer focus-visible:outline-2 focus-visible:outline-ring">
|
||||
<FormSectionTitle as="span">{t("characteristics")}</FormSectionTitle>
|
||||
</summary>
|
||||
<FieldSections
|
||||
items={asset.specification.fields}
|
||||
layout="rows"
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { FormActions, FormBody, FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { DialogFooter } from "@repo/shadcn-ui/components/dialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BrandApiError } from "../../shared/api";
|
||||
@ -26,17 +25,16 @@ export function DeleteBrandDialog(props: DeleteBrandDialogProps) {
|
||||
open
|
||||
title={t("brands.delete")}
|
||||
>
|
||||
<p>{t("brands.deleteConfirm", { name: brand.name })}</p>
|
||||
<p className="text-sm text-muted-foreground">{t("brands.deleteGuidance")}</p>
|
||||
{mutation.isError && (
|
||||
<p role="alert">
|
||||
{t(`brands.errors.${errorCode}`, { defaultValue: t("brands.deleteError") })}
|
||||
</p>
|
||||
)}
|
||||
<DialogFooter className="border-t pt-4">
|
||||
<Button disabled={mutation.isPending} onClick={onClose} type="button" variant="outline">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<FormBody>
|
||||
<p>{t("brands.deleteConfirm", { name: brand.name })}</p>
|
||||
<p className="text-sm text-muted-foreground">{t("brands.deleteGuidance")}</p>
|
||||
{mutation.isError && (
|
||||
<p role="alert">
|
||||
{t(`brands.errors.${errorCode}`, { defaultValue: t("brands.deleteError") })}
|
||||
</p>
|
||||
)}
|
||||
</FormBody>
|
||||
<FormActions cancelDisabled={mutation.isPending}>
|
||||
<Button
|
||||
disabled={mutation.isPending || brand.builtin}
|
||||
onClick={() => {
|
||||
@ -49,7 +47,7 @@ export function DeleteBrandDialog(props: DeleteBrandDialogProps) {
|
||||
>
|
||||
{t(mutation.isPending ? "brands.deleting" : "brands.delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</FormActions>
|
||||
</FormDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { FormActions, FormBody, FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { DialogFooter } from "@repo/shadcn-ui/components/dialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { CatalogItemDTO } from "../../../../common";
|
||||
@ -34,17 +33,16 @@ export function DeleteCatalogDefinitionDialog({
|
||||
open
|
||||
title={t("delete")}
|
||||
>
|
||||
<p>{t("deleteDefinitionConfirm", { name: item.values.name })}</p>
|
||||
<p className="text-sm text-muted-foreground">{t("deleteDefinitionGuidance")}</p>
|
||||
{error && <p role="alert">{t(`errors.${code}`, { defaultValue: t("deleteError") })}</p>}
|
||||
<DialogFooter className="border-t pt-4">
|
||||
<Button disabled={pending} onClick={onClose} type="button" variant="outline">
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<FormBody>
|
||||
<p>{t("deleteDefinitionConfirm", { name: item.values.name })}</p>
|
||||
<p className="text-sm text-muted-foreground">{t("deleteDefinitionGuidance")}</p>
|
||||
{error && <p role="alert">{t(`errors.${code}`, { defaultValue: t("deleteError") })}</p>}
|
||||
</FormBody>
|
||||
<FormActions cancelDisabled={pending}>
|
||||
<Button disabled={pending} onClick={onDelete} type="button" variant="destructive">
|
||||
{t(pending ? "deleting" : "delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</FormActions>
|
||||
</FormDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -30,7 +31,7 @@ export function AssetSupport(props: AssetSupportProps) {
|
||||
return (
|
||||
<section className={compact ? "space-y-3" : "space-y-3 rounded-md border p-4"}>
|
||||
<header className="flex flex-wrap items-center justify-between gap-3">
|
||||
{!compact && <h3 className="font-semibold">{t("support.title")}</h3>}
|
||||
{!compact && <FormSectionTitle>{t("support.title")}</FormSectionTitle>}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
disabled={!query.data || retired}
|
||||
|
||||
@ -27,7 +27,7 @@ export function WorkstationAssociationFields({
|
||||
const { t } = useTranslation("assets");
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-site`}>{t("site")}</Label>
|
||||
<SelectWithAction
|
||||
|
||||
@ -40,10 +40,12 @@ export function AccessFormFields(
|
||||
<>
|
||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||
<FormBody>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
{editableFields.map((field) => (
|
||||
<div
|
||||
className={["note", "permissions"].includes(field.type) ? "sm:col-span-2" : ""}
|
||||
className={
|
||||
["note", "permissions"].includes(field.type) ? "@min-[560px]:col-span-2" : ""
|
||||
}
|
||||
key={field.key}
|
||||
>
|
||||
<AccessFieldControl
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
||||
import { Label } from "@repo/shadcn-ui/components/label";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
@ -24,7 +25,7 @@ export function PermissionsControl({
|
||||
|
||||
return (
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-sm font-medium">{t(fieldKey)}</legend>
|
||||
<FormSectionTitle as="legend">{t(fieldKey)}</FormSectionTitle>
|
||||
{options.map((option) => (
|
||||
<div className="flex items-center gap-2" key={option.value}>
|
||||
<Checkbox
|
||||
|
||||
@ -49,7 +49,7 @@ export function ApplicationConfigurationForm(props: ApplicationConfigurationForm
|
||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||
<FormBody>
|
||||
<fieldset className="space-y-4" disabled={mutation.isPending}>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
||||
<SelectWithAction
|
||||
|
||||
@ -57,7 +57,7 @@ export function AssignmentForm(props: AssignmentFormProps) {
|
||||
<Label htmlFor={`${id}-position`}>{t("departmentPosition")}</Label>
|
||||
<Input id={`${id}-position`} maxLength={100} {...form.register("position")} />
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${id}-start`}>{t("startDate")}</Label>
|
||||
<Input id={`${id}-start`} required type="date" {...form.register("startDate")} />
|
||||
|
||||
@ -48,7 +48,7 @@ export function CertificateConfigurationForm(props: CertificateConfigurationForm
|
||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||
<FormBody>
|
||||
<fieldset className="space-y-4" disabled={mutation.isPending}>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
||||
<SelectWithAction
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { FormActions, FormBody, FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -19,7 +19,7 @@ export function DashboardQuickCreate(props: DashboardQuickCreateProps) {
|
||||
<>
|
||||
<FormDialog
|
||||
cancelLabel={t("cancel")}
|
||||
formActions={kind !== "asset" && kind !== "resource"}
|
||||
formActions
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
onClose();
|
||||
@ -30,35 +30,41 @@ export function DashboardQuickCreate(props: DashboardQuickCreateProps) {
|
||||
>
|
||||
{kind === "company" && <CompanyForm onSaved={saved} showTitle={false} />}
|
||||
{(kind === "asset" || kind === "resource") && (
|
||||
<BlockState empty={false} emptyText="" query={companies}>
|
||||
<SelectWithAction
|
||||
action={{ label: t("newCompany"), onClick: () => setAddingCompany(true) }}
|
||||
id="dashboard-create-company"
|
||||
label={t("companyLabel")}
|
||||
onValueChange={setSelectedCompany}
|
||||
options={
|
||||
companies.data?.companies
|
||||
.filter((item) => item.active)
|
||||
.map((item) => ({ value: item.id, label: item.name })) ?? []
|
||||
}
|
||||
placeholder={t("dashboard.chooseCompany")}
|
||||
value={selectedCompany}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">{t("dashboard.chooseCompany")}</p>
|
||||
<Button
|
||||
disabled={!selectedCompany}
|
||||
onClick={() =>
|
||||
onNavigate({
|
||||
area: "company",
|
||||
id: selectedCompany,
|
||||
section: kind === "asset" ? "inventory" : "resources",
|
||||
create: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("dashboard.continue")}
|
||||
</Button>
|
||||
</BlockState>
|
||||
<>
|
||||
<FormBody>
|
||||
<BlockState empty={false} emptyText="" query={companies}>
|
||||
<SelectWithAction
|
||||
action={{ label: t("newCompany"), onClick: () => setAddingCompany(true) }}
|
||||
id="dashboard-create-company"
|
||||
label={t("companyLabel")}
|
||||
onValueChange={setSelectedCompany}
|
||||
options={
|
||||
companies.data?.companies
|
||||
.filter((item) => item.active)
|
||||
.map((item) => ({ value: item.id, label: item.name })) ?? []
|
||||
}
|
||||
placeholder={t("dashboard.chooseCompany")}
|
||||
value={selectedCompany}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">{t("dashboard.chooseCompany")}</p>
|
||||
</BlockState>
|
||||
</FormBody>
|
||||
<FormActions>
|
||||
<Button
|
||||
disabled={!(selectedCompany && companies.isSuccess)}
|
||||
onClick={() =>
|
||||
onNavigate({
|
||||
area: "company",
|
||||
id: selectedCompany,
|
||||
section: kind === "asset" ? "inventory" : "resources",
|
||||
create: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("dashboard.continue")}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</>
|
||||
)}
|
||||
</FormDialog>
|
||||
{addingCompany && (
|
||||
|
||||
@ -76,7 +76,7 @@ export function FolderMappingForm(props: FolderMappingFormProps) {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-letter`}>{t("folders.letter")}</Label>
|
||||
<Input
|
||||
|
||||
@ -75,7 +75,7 @@ export function MailboxUseForm(props: MailboxUseFormProps) {
|
||||
value={values.personId}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-start`}>{t("mailbox.start")}</Label>
|
||||
<Input
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@ -14,7 +15,7 @@ export function MailboxRelations(props: MailboxRelationsProps) {
|
||||
return (
|
||||
<section className="space-y-3 border-t pt-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h3 className="font-medium">{t("people")}</h3>
|
||||
<FormSectionTitle>{t("people")}</FormSectionTitle>
|
||||
<Button onClick={() => setEditor("new")} size="sm" variant="outline">
|
||||
{t("mailbox.linkPerson")}
|
||||
</Button>
|
||||
|
||||
@ -53,7 +53,7 @@ export function PersonForm(props: PersonFormProps) {
|
||||
onChange={setPhotoFile}
|
||||
person={person}
|
||||
/>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${id}-name`}>{t("name")}</Label>
|
||||
<Input id={`${id}-name`} maxLength={100} required {...form.register("name")} />
|
||||
@ -65,7 +65,7 @@ export function PersonForm(props: PersonFormProps) {
|
||||
</div>
|
||||
</FormSection>
|
||||
<FormSection title={t("personForm.contact")} variant="plain">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${id}-email`}>{t("emailOptional")}</Label>
|
||||
<Input id={`${id}-email`} maxLength={254} type="email" {...form.register("email")} />
|
||||
|
||||
@ -52,7 +52,7 @@ export function PrinterConfigurationForm(props: PrinterConfigurationFormProps) {
|
||||
<form className="flex min-h-0 flex-1 flex-col overflow-hidden" onSubmit={submit}>
|
||||
<FormBody>
|
||||
<fieldset className="space-y-4" disabled={mutation.isPending}>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-profile`}>{t("mail.profile")}</Label>
|
||||
<SelectWithAction
|
||||
|
||||
@ -62,7 +62,7 @@ export function RemoteConnectionForm(props: RemoteConnectionFormProps) {
|
||||
value={v.personId}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<RemoteEndpointFields
|
||||
assetOptions={assetOptions}
|
||||
company={company}
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { ResourceEditor } from "@metis/resources/client";
|
||||
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function SoftwareResourceCreateDialog({
|
||||
companyId,
|
||||
kind,
|
||||
onClose,
|
||||
onSaved,
|
||||
}: {
|
||||
companyId: string;
|
||||
kind: "os" | "application";
|
||||
onClose: () => void;
|
||||
onSaved: (id: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation("companies");
|
||||
|
||||
return (
|
||||
<FormDialog
|
||||
cancelLabel={t("cancel")}
|
||||
formActions
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
open
|
||||
title={t(kind === "os" ? "software.newOs" : "applications.newResource")}
|
||||
wide
|
||||
>
|
||||
<ResourceEditor
|
||||
allowQuickCreate={false}
|
||||
companyId={companyId}
|
||||
initialKind={kind}
|
||||
onSaved={onSaved}
|
||||
/>
|
||||
</FormDialog>
|
||||
);
|
||||
}
|
||||
@ -12,7 +12,6 @@ import {
|
||||
EntityStatus,
|
||||
EntityTable,
|
||||
} from "@repo/rdx-ui/components/entity-list";
|
||||
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
||||
import { useId, useState } from "react";
|
||||
@ -21,7 +20,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { DeleteRecordDialog } from "../../../shared/ui/delete-record-dialog";
|
||||
import { SoftwareHistoryDialog } from "../../history/ui/software-history-dialog";
|
||||
import { softwareApi, softwareKeys } from "../../shared/api/software-api";
|
||||
import { SoftwareInstallationForm } from "../../shared/ui/software-installation-form";
|
||||
import { SoftwareInstallationEditorDialog } from "../../shared/ui/software-installation-editor-dialog";
|
||||
|
||||
export function SoftwareInstallationsPanel({
|
||||
companyId,
|
||||
@ -255,26 +254,15 @@ export function SoftwareInstallationsPanel({
|
||||
/>
|
||||
)}
|
||||
{editor && assetId && (
|
||||
<FormDialog
|
||||
cancelLabel={t("cancel")}
|
||||
formActions
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setEditor(undefined);
|
||||
}
|
||||
}}
|
||||
open
|
||||
title={t(edited ? "software.edit" : "software.new")}
|
||||
>
|
||||
<SoftwareInstallationForm
|
||||
assetId={assetId}
|
||||
companyId={companyId}
|
||||
installation={edited}
|
||||
licenseOptions={licenseOptions}
|
||||
onSaved={() => setEditor(undefined)}
|
||||
options={options}
|
||||
/>
|
||||
</FormDialog>
|
||||
<SoftwareInstallationEditorDialog
|
||||
assetId={assetId}
|
||||
companyId={companyId}
|
||||
installation={edited}
|
||||
licenseOptions={licenseOptions}
|
||||
onClose={() => setEditor(undefined)}
|
||||
onSaved={() => setEditor(undefined)}
|
||||
options={options}
|
||||
/>
|
||||
)}
|
||||
{viewed && (
|
||||
<SoftwareHistoryDialog
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
import { FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import type { ComponentProps } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { SoftwareInstallationForm } from "./software-installation-form";
|
||||
|
||||
export function SoftwareInstallationEditorDialog({
|
||||
onClose,
|
||||
...formProps
|
||||
}: ComponentProps<typeof SoftwareInstallationForm> & { onClose: () => void }) {
|
||||
const { t } = useTranslation("companies");
|
||||
|
||||
return (
|
||||
<FormDialog
|
||||
cancelLabel={t("cancel")}
|
||||
formActions
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
open
|
||||
title={t(formProps.installation ? "software.edit" : "software.new")}
|
||||
>
|
||||
<SoftwareInstallationForm {...formProps} />
|
||||
</FormDialog>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ResourceEditor } from "@metis/resources/client";
|
||||
import { ContextHelp } from "@repo/rdx-ui/components/context-help";
|
||||
import { FormActions, FormBody, FormDialog } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { FormActions, FormBody } from "@repo/rdx-ui/components/form-dialog";
|
||||
import { SelectWithAction } from "@repo/rdx-ui/components/select-with-action";
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { Input } from "@repo/shadcn-ui/components/input";
|
||||
@ -14,6 +13,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { SoftwareInstallationDTO, SoftwareInstallationInput } from "../../../../common";
|
||||
import { SOFTWARE_STATE_TRANSITIONS, SoftwareInstallationInputSchema } from "../../../../common";
|
||||
import { SoftwareResourceCreateDialog } from "../../create/ui/software-resource-create-dialog";
|
||||
import { SoftwareApiError, softwareApi, softwareKeys } from "../api/software-api";
|
||||
|
||||
export function SoftwareInstallationForm({
|
||||
@ -90,7 +90,7 @@ export function SoftwareInstallationForm({
|
||||
{t("applications.newResource")}
|
||||
</Button>
|
||||
)}
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-version`}>{t("applications.version")}</Label>
|
||||
<Input id={`${prefix}-version`} maxLength={100} {...form.register("version")} />
|
||||
@ -160,28 +160,15 @@ export function SoftwareInstallationForm({
|
||||
</FormActions>
|
||||
</form>
|
||||
{quick && (
|
||||
<FormDialog
|
||||
cancelLabel={t("cancel")}
|
||||
formActions
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setQuick(undefined);
|
||||
}
|
||||
<SoftwareResourceCreateDialog
|
||||
companyId={companyId}
|
||||
kind={quick}
|
||||
onClose={() => setQuick(undefined)}
|
||||
onSaved={(id) => {
|
||||
form.setValue("resourceId", id);
|
||||
setQuick(undefined);
|
||||
}}
|
||||
open
|
||||
title={t(quick === "os" ? "software.newOs" : "applications.newResource")}
|
||||
wide
|
||||
>
|
||||
<ResourceEditor
|
||||
allowQuickCreate={false}
|
||||
companyId={companyId}
|
||||
initialKind={quick}
|
||||
onSaved={(id) => {
|
||||
form.setValue("resourceId", id);
|
||||
setQuick(undefined);
|
||||
}}
|
||||
/>
|
||||
</FormDialog>
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -54,7 +54,7 @@ export function TeleworkAuthorizationForm(props: TeleworkAuthorizationFormProps)
|
||||
label={t("telework.active")}
|
||||
onCheckedChange={(active) => form.setValue("active", active)}
|
||||
/>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-start`}>{t("telework.startsOn")}</Label>
|
||||
<Input
|
||||
|
||||
@ -73,7 +73,7 @@ export function WorkProfileForm(props: WorkProfileFormProps) {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
{selectors.map((selector) => {
|
||||
const fixed = !!profile && selector.field !== "accountId";
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ export function IntegrationSummary({
|
||||
}));
|
||||
|
||||
return (
|
||||
<dl className="grid gap-2 sm:grid-cols-2">
|
||||
<dl className="grid gap-2 @min-[560px]:grid-cols-2">
|
||||
{entries.map((entry) => (
|
||||
<div key={entry.label}>
|
||||
<dt className="text-sm text-muted-foreground">{entry.label}</dt>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { DetailField } from "@repo/rdx-ui/components/detail-field";
|
||||
import { FieldSections } from "@repo/rdx-ui/components/field-sections";
|
||||
import { FormSectionTitle } from "@repo/rdx-ui/components/form-section-title";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { ResourceDTO, ResourceReferenceDTO } from "../../../../common/inventory";
|
||||
@ -25,10 +26,10 @@ export function ResourceDetail({
|
||||
: String(value);
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="@container space-y-5">
|
||||
{!compact && <p className="text-sm text-muted-foreground">{t("snapshotNotice")}</p>}
|
||||
{!compact && (
|
||||
<dl className="grid gap-4 sm:grid-cols-2">
|
||||
<dl className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div>
|
||||
<dt className="text-sm text-muted-foreground">{t("template")}</dt>
|
||||
<dd>{resource.specification.templateName}</dd>
|
||||
@ -65,9 +66,9 @@ export function ResourceDetail({
|
||||
)}
|
||||
{!compact && (
|
||||
<>
|
||||
<h3 className="font-medium">
|
||||
<FormSectionTitle>
|
||||
{t("history")} ({resource.history.length})
|
||||
</h3>
|
||||
</FormSectionTitle>
|
||||
<ol className="space-y-4">
|
||||
{[...resource.history].reverse().map((revision) => (
|
||||
<li className="space-y-2 rounded-md border p-3 text-sm" key={revision.id}>
|
||||
|
||||
@ -32,7 +32,7 @@ export function ResourceIntegrationFields({
|
||||
|
||||
if (mailbox) {
|
||||
return (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`${prefix}-kind`}>{t("mailboxKind")}</Label>
|
||||
<SelectWithAction
|
||||
@ -104,7 +104,7 @@ export function ResourceIntegrationFields({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="grid gap-4 @min-[560px]:grid-cols-2">
|
||||
{(["resourceTypeId", "systemId", "environmentId"] as const).map((key) => {
|
||||
const kind =
|
||||
key === "resourceTypeId"
|
||||
|
||||
@ -48,7 +48,7 @@ export function ConfirmDeleteDialog({
|
||||
</p>
|
||||
)}
|
||||
</FormBody>
|
||||
<FormActions>
|
||||
<FormActions cancelDisabled={pending}>
|
||||
<Button
|
||||
disabled={pending}
|
||||
onClick={async () => {
|
||||
|
||||
@ -2,16 +2,18 @@ import type { ReactNode } from "react";
|
||||
|
||||
import { ChevronRightIcon } from "../lib/icons";
|
||||
|
||||
import { FormSectionTitle } from "./form-section-title";
|
||||
|
||||
/** Grupo de consulta compartido por fichas y plantillas; abierto inicialmente. */
|
||||
export function DetailSection({ title, children }: { title: string; children: ReactNode }) {
|
||||
return (
|
||||
<details className="@container group/detail-section" open>
|
||||
<summary className="flex cursor-pointer list-none items-center gap-2 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden">
|
||||
<summary className="flex cursor-pointer list-none items-center gap-2 py-2 focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden">
|
||||
<ChevronRightIcon
|
||||
aria-hidden="true"
|
||||
className="size-3 shrink-0 transition-transform group-open/detail-section:rotate-90 motion-reduce:transition-none"
|
||||
className="size-3 shrink-0 text-muted-foreground transition-transform group-open/detail-section:rotate-90 motion-reduce:transition-none"
|
||||
/>
|
||||
{title}
|
||||
<FormSectionTitle as="span">{title}</FormSectionTitle>
|
||||
</summary>
|
||||
<div className="space-y-3 border-t border-border/60 px-5 pb-2 [&>dl>div:first-child]:pt-2.5 [&>dl>div:last-child]:pb-2.5">
|
||||
{children}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
||||
import type { Table as TableModel } from "@tanstack/react-table";
|
||||
import { useId } from "react";
|
||||
|
||||
import { entityListLabels } from "../lib/entity-list";
|
||||
|
||||
import { FormDialog } from "./form-dialog";
|
||||
|
||||
export function EntityTableColumnsDialog<T>({
|
||||
table,
|
||||
locale,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: {
|
||||
table: TableModel<T>;
|
||||
locale: string;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) {
|
||||
const id = useId();
|
||||
const labels = entityListLabels(locale);
|
||||
|
||||
return (
|
||||
<FormDialog
|
||||
cancelLabel={locale.startsWith("en") ? "Close" : "Cerrar"}
|
||||
onOpenChange={onOpenChange}
|
||||
open={open}
|
||||
title={labels.columns}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{table.getAllLeafColumns().map((column) => (
|
||||
<label
|
||||
className="flex items-center gap-2"
|
||||
htmlFor={`${id}-column-${column.id}`}
|
||||
key={column.id}
|
||||
>
|
||||
<Checkbox
|
||||
checked={column.getIsVisible()}
|
||||
disabled={!column.getCanHide()}
|
||||
id={`${id}-column-${column.id}`}
|
||||
onCheckedChange={(checked) => column.toggleVisibility(checked === true)}
|
||||
/>
|
||||
{String(column.columnDef.header)}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</FormDialog>
|
||||
);
|
||||
}
|
||||
@ -1,11 +1,10 @@
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { Checkbox } from "@repo/shadcn-ui/components/checkbox";
|
||||
import type { Table as TableModel } from "@tanstack/react-table";
|
||||
import { useId, useState } from "react";
|
||||
|
||||
import { entityListLabels } from "../lib/entity-list";
|
||||
|
||||
import { FormDialog } from "./form-dialog";
|
||||
import { EntityTableColumnsDialog } from "./entity-table-columns-dialog";
|
||||
import { SelectWithAction } from "./select-with-action";
|
||||
|
||||
export function EntityTableConfiguration<T>({
|
||||
@ -60,30 +59,12 @@ export function EntityTableConfiguration<T>({
|
||||
value={sorting[0] ? `${sorting[0].id}:${sorting[0].desc ? "desc" : "asc"}` : "initial"}
|
||||
/>
|
||||
</div>{" "}
|
||||
<FormDialog
|
||||
cancelLabel={locale.startsWith("en") ? "Close" : "Cerrar"}
|
||||
<EntityTableColumnsDialog
|
||||
locale={locale}
|
||||
onOpenChange={setColumnsOpen}
|
||||
open={columnsOpen}
|
||||
title={labels.columns}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{table.getAllLeafColumns().map((column) => (
|
||||
<label
|
||||
className="flex items-center gap-2"
|
||||
htmlFor={`${id}-column-${column.id}`}
|
||||
key={column.id}
|
||||
>
|
||||
<Checkbox
|
||||
checked={column.getIsVisible()}
|
||||
disabled={!column.getCanHide()}
|
||||
id={`${id}-column-${column.id}`}
|
||||
onCheckedChange={(checked) => column.toggleVisibility(checked === true)}
|
||||
/>
|
||||
{String(column.columnDef.header)}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</FormDialog>
|
||||
table={table}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
|
||||
import { ChevronRightIcon } from "../lib/icons";
|
||||
|
||||
import { DetailSection } from "./detail-section";
|
||||
import { FormSectionTitle } from "./form-section-title";
|
||||
|
||||
export type FieldSection = { id: string; name: string; description: string };
|
||||
|
||||
@ -99,10 +100,10 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
|
||||
{styles.compact && (
|
||||
<ChevronRightIcon
|
||||
aria-hidden="true"
|
||||
className="size-3 shrink-0 transition-transform group-open/field-section:rotate-90 motion-reduce:transition-none"
|
||||
className="size-3 shrink-0 text-muted-foreground transition-transform group-open/field-section:rotate-90 motion-reduce:transition-none"
|
||||
/>
|
||||
)}
|
||||
{section.name}
|
||||
<FormSectionTitle as="span">{section.name}</FormSectionTitle>
|
||||
</summary>
|
||||
<div className={styles.body}>
|
||||
{section.description && <p className={styles.description}>{section.description}</p>}
|
||||
@ -113,7 +114,9 @@ export function FieldSections<T extends { sectionId?: string | undefined }>({
|
||||
})}
|
||||
{ungrouped.length > 0 && (
|
||||
<section className="col-span-full space-y-3">
|
||||
<h4 className={styles.ungrouped}>{ungroupedLabel}</h4>
|
||||
<div className={styles.compact ? "pl-5" : undefined}>
|
||||
<FormSectionTitle as="h4">{ungroupedLabel}</FormSectionTitle>
|
||||
</div>
|
||||
{content(ungrouped)}
|
||||
</section>
|
||||
)}
|
||||
@ -129,8 +132,8 @@ function fieldSectionStyles(layout: FieldSectionLayout, values: boolean) {
|
||||
? "@container"
|
||||
: "@container rounded-md border";
|
||||
const summary = compact
|
||||
? "flex cursor-pointer list-none items-center gap-2 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden"
|
||||
: `cursor-pointer text-sm font-medium focus-visible:outline-2 focus-visible:outline-ring ${values ? "py-2" : "rounded-md px-4 py-3"}`;
|
||||
? "flex cursor-pointer list-none items-center gap-2 py-2 focus-visible:outline-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden"
|
||||
: `cursor-pointer focus-visible:outline-2 focus-visible:outline-ring ${values ? "py-2" : "rounded-md px-4 py-3"}`;
|
||||
const body =
|
||||
layout === "form"
|
||||
? "space-y-3 border-t border-border/60 pt-3"
|
||||
@ -150,20 +153,17 @@ function fieldSectionStyles(layout: FieldSectionLayout, values: boolean) {
|
||||
body,
|
||||
description,
|
||||
...FIELD_SECTION_LAYOUTS[layout],
|
||||
ungrouped: compact
|
||||
? "pl-5 text-xs font-medium uppercase tracking-wide text-muted-foreground"
|
||||
: "text-sm font-medium",
|
||||
};
|
||||
}
|
||||
|
||||
const FIELD_SECTION_LAYOUTS = {
|
||||
fields: {
|
||||
content: "grid gap-4 sm:grid-cols-2 @min-[900px]:grid-cols-3",
|
||||
wrapper: "grid items-start gap-4 @min-[900px]:grid-cols-2",
|
||||
content: "grid gap-4 @min-[560px]:grid-cols-2 @min-[900px]:grid-cols-3",
|
||||
wrapper: "grid items-stretch gap-4 @min-[900px]:grid-cols-2",
|
||||
},
|
||||
rows: {
|
||||
content: "divide-y divide-border/50",
|
||||
wrapper: "grid items-start gap-x-8 gap-y-3 @min-[900px]:grid-cols-2",
|
||||
wrapper: "grid items-stretch gap-x-8 gap-y-3 @min-[900px]:grid-cols-2",
|
||||
},
|
||||
form: {
|
||||
content:
|
||||
|
||||
@ -75,9 +75,11 @@ export function FormDialog({
|
||||
export function FormActions({
|
||||
children,
|
||||
leadingActions,
|
||||
cancelDisabled = false,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
leadingActions?: ReactNode;
|
||||
cancelDisabled?: boolean;
|
||||
}) {
|
||||
const cancelLabel = useContext(FormCancelContext);
|
||||
|
||||
@ -85,7 +87,9 @@ export function FormActions({
|
||||
<DialogFooter className="shrink-0 border-t bg-popover py-4">
|
||||
{leadingActions && <div className="flex sm:mr-auto">{leadingActions}</div>}
|
||||
{cancelLabel && (
|
||||
<DialogClose render={<Button type="button" variant="outline" />}>{cancelLabel}</DialogClose>
|
||||
<DialogClose render={<Button disabled={cancelDisabled} type="button" variant="outline" />}>
|
||||
{cancelLabel}
|
||||
</DialogClose>
|
||||
)}
|
||||
{children}
|
||||
</DialogFooter>
|
||||
@ -100,8 +104,8 @@ export function FormBody({ children }: { children: ReactNode }) {
|
||||
<div
|
||||
className={
|
||||
layout === "picker"
|
||||
? "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-1 pt-1 pb-5 [scrollbar-gutter:stable] [&>*]:shrink-0"
|
||||
: "min-h-0 flex-1 space-y-5 overflow-y-auto px-1 pt-1 pb-5"
|
||||
? "@container flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-1 pt-1 pb-5 [scrollbar-gutter:stable] [&>*]:shrink-0"
|
||||
: "@container min-h-0 flex-1 space-y-5 overflow-y-auto px-1 pt-1 pb-5"
|
||||
}
|
||||
data-slot="form-body"
|
||||
>
|
||||
|
||||
@ -8,11 +8,11 @@ export function FormSectionTitle({
|
||||
}: {
|
||||
children: ReactNode;
|
||||
id?: string;
|
||||
as?: "h3" | "h4" | "h5";
|
||||
as?: "h3" | "h4" | "h5" | "legend" | "span";
|
||||
}) {
|
||||
return (
|
||||
<Heading
|
||||
className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-4"
|
||||
className={`text-xs font-semibold uppercase tracking-wide text-muted-foreground ${Heading === "span" ? "" : "mb-4"}`}
|
||||
id={id}
|
||||
>
|
||||
{children}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user