Buscar en la documentación

Componentes base

Componentes reutilizables de Trazo, cuándo usar cada uno y sus reglas.

Módulo design11 min de lecturaVer en GitHub

Viven en src/shared/ui/ y son los únicos que tocan Quasar directamente. Las pantallas los usan sin conocer la librería. En el modo desarrollo, la guía visual (/guia-visual) los muestra todos con sus estados, para revisarlos en las cuatro vistas:

  • La base: marca, íconos, botones, etiquetas, campos, mensajes y estados.
  • Formularios: casilla, chips, lista de opciones, control segmentado, fecha y hora, rango de fechas y código de 6 dígitos.
  • Datos: tabla con orden y selección, paginación, los tres gráficos, ícono de información, menú de opciones, bloque para copiar y QR.
  • Avisos y capas: avisos, panel lateral o inferior, panel de filtros, diálogo de formulario y diálogo del código.

La E2E de accesibilidad la revisa con axe en las cuatro vistas.

ComponentePara qué
AppButtonAcciones. Variantes primary, secondary, danger y link; con loading queda bloqueado y lo anuncia (aria-busy). Alto táctil de 44 px en celular y tablet. Con to es un enlace de la app con el mismo aspecto (para navegar, como “Ver su actividad”); sin to es un botón (para acciones). Desde portátil mide 36 px ($control-height-compact).
AppBackLinkRegreso al listado de una sección (”‹ Usuarios”, ”‹ Roles”), arriba del título. Flecha y texto en gris secundario, sin subrayado; al pasar el cursor, fondo suave y texto oscuro. Para lectores de pantalla se anuncia “Volver a Usuarios”.
useFitCountCuenta cuántas filas de una lista caben en el alto de su contenedor (con ResizeObserver), para mostrar solo esas y resumir el resto (“Y N más”). El contenedor necesita un alto que no dependa de su contenido, por ejemplo una tarjeta con contain: size. Lo usa la tarjeta de permisos de la ficha de usuario.
useTextOverflowDice si un texto recortado (una línea, line-clamp) se sale de su caja, y vuelve a medir cuando cambia el ancho. Así “Ver completo” aparece solo cuando hace falta. Lo usa la nota del historial de incidentes.
AppIconÍconos propios (ver Íconos).
AppInfoTipÍcono de información con una ayuda oculta. Ver la regla abajo.
AppBadgeEtiqueta sólida para estados. Recibe un tono; el contraste ya viene resuelto (texto blanco). Esquinas de 4 px.
AppDotPunto de color con texto, para prioridades. Distinto a propósito de la etiqueta del estado.
AppAvatarCírculo con las iniciales de una persona, fondo oscuro neutro y texto blanco, en tres tamaños (size): sm 24 px ($avatar-size-sm) en las filas del listado, md 32 px ($avatar-size) en el historial y el chat, y lg 40 px ($avatar-size-lg) en la ficha del incidente. Es decorativo: el nombre va siempre en texto (visible u oculto para lectores de pantalla).
AppCardSuperficie con título y acciones opcionales.
AppTextField · AppSelectCampos con etiqueta arriba, ayuda en el ícono de información y error siempre visible debajo. Comparten la envoltura AppField. Con type="password", el campo trae un botón para mostrar u ocultar la contraseña; textarea empieza con espacio para varias líneas; inputmode="numeric" abre el teclado numérico en el celular; con counter y maxlength muestra “usados/máximo” bajo el campo, a la derecha (el título del incidente lo usa). AppSelect muestra su texto de ejemplo (en gris) cuando no hay valor, y no muestra el código interno del valor mientras sus opciones no han llegado. Con filterable se escribe para filtrar las opciones, sin importar tildes ni mayúsculas (listas largas, como las personas). Con inline, la etiqueta va a la izquierda del control, en la misma fila, para barras donde la etiqueta arriba ocupa demasiado (“Agrupar por” y “Ordenar por” del listado). Con búsqueda, el campo siempre ocupa una línea: un nombre largo se corta con ”…” y, mientras la lista está abierta, se oculta para escribir sobre el campo limpio. La opción elegida va en negrita con ✓.
AppDateTimeFieldFecha y hora con el diseño de Trazo, no el del navegador. El campo cerrado se ve como un selector (“25 de oct, 10:30 a. m.”). En portátil y monitor abre un menú con el calendario y la lista de horas lado a lado; en celular y tablet, un panel que sube desde abajo, con las horas en una fila deslizable. Horas cada 30 minutos; los días y horas pasados no se pueden elegir. Si recibe clearLabel, el pie ofrece vaciar el campo (p. ej. “Usar la sugerida”). El valor es ISO en UTC o null. La lógica vive aparte, en dateTimeParts.ts, con sus pruebas.
AppPopupFieldBase de los campos que abren un panel: se ve como un selector y abre un menú junto al campo en portátil y monitor (sobre AppMenu, donde quepa entero), o un panel inferior de borde a borde en celular y tablet. El menú puede usar hasta el 90 % del alto de la ventana (layout.popupMaxHeight), para que el calendario no tenga scroll en portátiles bajos. Lo usan AppDateTimeField y AppDateRangeField.
AppDateRangeFieldRango de fechas sin hora, para filtros (“1 de oct – 22 de oct”). Toma días completos en la hora local: desde el inicio del primer día hasta el final del último. La lógica vive en dateRange.ts, con sus pruebas. El calendario mide las semanas del mes (cinco o seis), sin el espacio fijo que deja Quasar.
AppCheckboxCasilla con el estilo de Trazo y alto táctil en celular y tablet. Con disabled queda visible pero sin efecto. Con indeterminate se ve a medias (p. ej. “Todos” con una parte marcada) y al tocarla queda marcada.
AppRadioListUna opción entre varias, cada una con su nombre en negrita y una línea de descripción debajo (p. ej. el rol de una persona). Cada fila ocupa todo el ancho y se elige tocándola entera; la elegida lleva borde azul. Grupo con legend y error asociado.
AppCodeFieldCódigo numérico de un solo uso (6 dígitos por defecto) en un solo campo: teclado numérico, autocomplete="one-time-code", solo acepta dígitos (pegar “123 456” funciona) y los muestra separados.
AppQrCodeCódigo QR dibujado como SVG desde la matriz de la librería qrcode, sin v-html y con los colores de los tokens. Imagen con nombre para lectores de pantalla.
AppCopyBlockTexto para copiar (una clave, un código) con el botón “Copiar”, que cambia a “Copiado” y lo anuncia.
AppChoiceChipsSelección múltiple como chips que se activan y desactivan, cómoda para tocar. Activo: fondo azul y texto blanco.
AppMenuBase de todos los menús (envuelve QMenu). Antes de mostrarse mide su tamaño y el espacio libre alrededor del botón, y se abre donde cabe entero: abajo; si no, arriba; si no, al lado (izquierda o derecha, donde haya más espacio), corrido para quedar dentro de la ventana; y si nada alcanza, baja la página lo justo y se abre abajo. Solo un menú más alto que la ventana tiene desplazamiento interno. Mientras mide no se ve, así no salta de un lugar a otro. La regla vive aparte, en menuPlacement.ts, con sus pruebas; la separación con el botón y el margen con los bordes son tokens (layout.menuGap, layout.menuViewportMargin). align="right" alinea el menú al borde derecho del botón (ADR-0075).
AppActionMenuMenú de opciones que se abre desde el botón que lo contiene (sobre AppMenu). Cada opción puede llevar ícono, marcarse como peligrosa, quedar desactivada (visible pero sin efecto) o separarse de las anteriores con una línea; se cierra al elegir. El tipo de las opciones está en menu.ts.
Estilo común de los desplegablesTodos los desplegables (opciones de AppSelect, AppActionMenu, el menú de la cuenta y “Agregar widget”; todos menos AppSelect se abren con AppMenu) usan la misma opción de lista: el mixin menu-item (design/tokens/menu.scss). Letra de 13 px, filas de 44 px en celular y tablet y de 40 px desde portátil, y al pasar el cursor o al moverse con el teclado, el fondo azulado de la página. El panel flotante es igual para todos (estilo global de .q-menu): superficie casi blanca, borde suave, sombra y esquinas de 4 px ($radius-menu). Un desplegable nuevo incluye el mixin y no escribe estilos propios para sus opciones.
AppSegmentedUna opción entre pocas, como botones unidos (“7 días · 30 días · 90 días”). Se anuncia como grupo de opciones (radiogroup); la activa va en azul con texto blanco.
AppPagination“1–20 de 534”, páginas con saltos (…) y tamaño de página. En celular, solo anterior, “Página 3 de 27” y siguiente. La página actual se anuncia con aria-current.
AppDataTableTabla de los listados en portátil y monitor. Las columnas se declaran como datos (dataTable.ts): las que ordenan anuncian el orden con aria-sort; emphasis da texto secundario o corta el texto largo con ”…”; hiddenLabel deja el nombre solo para lectores de pantalla. Cada celda se dibuja con el slot cell-<columna>. Las filas se abren con clic o Enter, y la elegida se resalta. La usan incidentes y usuarios.
AppSheetPanel sobre la página: desde la derecha en portátil y monitor (de arriba abajo, pegado al borde y sin esquinas redondeadas), desde abajo en celular y tablet (a todo lo ancho; o siempre desde abajo con side="bottom"). Título, botón de cerrar, una barra opcional bajo el título (toolbar, para pestañas o filtros que no se desplazan), contenido que se desplaza y un pie opcional fijo. Emite shown cuando terminó de abrirse, para mover el foco dentro sin que el diálogo lo recupere. El margen que Quasar deja alrededor de los diálogos se quita para todos los paneles en theme/global.scss.
AppFilterSheetPanel de filtros de celular y tablet, hecho sobre AppSheet: sube desde abajo, con los campos en el slot y un pie fijo con “Limpiar” y el botón de aplicar.
GráficosAppBarList, AppDonutChart y AppColumnChart. Ver Gráficos.
AppAlertAviso siempre visible dentro de la página (error de un formulario o información imprescindible). Tonos danger (rol alert) e info (rol status); fondo de superficie con borde fino de color (sin franja a la izquierda), nunca fondo tintado. Ícono, texto y acción quedan centrados en una línea; la acción baja si no cabe.
AppSkeleton · AppEmptyState · AppErrorStateEstados de carga, vacío y error. El de error usa el catálogo de mensajes y ofrece “Reintentar”.
AppFormDialogDiálogo con un formulario corto: título, explicación opcional, error general, los campos en el slot y Cancelar más el botón de guardar (en rojo si la acción es delicada). No se cierra solo: quien lo usa lo cierra cuando el guardado sale bien, así los errores quedan a la vista. Envuelve el formulario en un div, porque Quasar solo deja hacer clic en el primer div del diálogo.
AppCodeDialogSobre AppFormDialog: pide el código actual de la aplicación antes de una acción sensible (regenerar códigos, desactivar el MFA, confirmar un cambio de administración) y muestra el error de la API sin cerrarse.
useFeedback()confirm() abre el diálogo de confirmación (devuelve true o false); notify() y notifyError() muestran notificaciones arriba a la derecha (abajo está el dock). notify() admite una acción (p. ej. “Abrir”); con ella el aviso dura 8 s en lugar de 4. Los errores no se cierran solos.
useLeaveGuard(dirty, mensaje)Avisa antes de salir con cambios sin guardar: al cambiar de sección pide confirmación con el estilo de Trazo; al cerrar o recargar la pestaña, el navegador muestra su propio aviso.
useViewport()Vista actual (mobile, tablet, laptop o desktop) y atajos como isMobile, isLaptopUp e isDesktop.

Ícono de información

Toda ayuda o aclaración que no sea imprescindible va en AppInfoTip, para mantener las pantallas limpias:

  • Se muestra al pasar el cursor, al enfocarlo con el teclado o al tocarlo en el celular; se cierra al salir, con Escape o al tocar fuera.
  • La burbuja calcula su posición para no salirse nunca de la pantalla.
  • Es accesible: el botón tiene nombre propio y la ayuda está asociada con aria-describedby.

Nunca se usa para errores, mensajes de validación ni instrucciones necesarias para completar una acción: eso siempre queda visible.

Reglas comunes

  • Estilos en un solo archivo por componente, mobile first, con bloques tablet, laptop y desktop.
  • Clases en BEM (bloque__elemento--modificador), verificado por Stylelint.
  • Fondo de color siempre con texto negro o blanco según contraste.
  • Comentarios: encabezado breve en cada archivo y comentarios cortos solo donde aportan.

Dónde está en el código