Selectores con búsqueda en el propio campo
Componente base: src/components/ui/searchable-combobox.tsx (SearchableCombobox). Reemplaza el patrón anterior (botón que abría un desplegable con otro buscador dentro) por el patrón ARIA «combobox con lista»: el campo es un <input role="combobox">; se hace clic, se abre la lista y se escribe ahí mismo.
Asociación de etiqueta (15/10/2026): si el campo usa etiqueta externa, transmitir su id hasta el input y asociar FieldLabel htmlFor al mismo valor. Usar useId para varias instancias. CitizenCombobox y LocationCombobox aceptan ahora id; al recibirlo usan la etiqueta externa como nombre en lugar de sobrescribirla con una etiqueta ARIA genérica. Sin id conservan el nombre de respaldo. DateField nombra su diálogo de calendario «Elegir fecha» y admite calendarLabel para contextualizarlo; no añadir otro calendario ni otro Popover.
Lado de la lista (21/10/2026): la lista se abre hacia abajo; si debajo del campo hay menos de 280 px y arriba hay más sitio, se abre hacia arriba, para mostrar la lista completa (hasta 300 px) en lugar de encogerla a una o dos opciones cuando el campo está cerca del borde inferior de la ventana. Se decide al abrir (clic, flecha o escritura) en SearchableCombobox, que usan también EntityCombobox y OptionCombobox. Select y DropdownMenu ya cambian de lado por la colisión de Radix, porque su alto no se ata al espacio disponible.
Uniformización de todos los selectores (2026-10-08)
Lista de opciones común. Todos los desplegables usan el mismo contenedor y las mismas filas (clases planas en globals.css, sin depender de @media (hover)):
.sgd-listbox: fondo--popover, borde de 1 px, esquinas de control, sombra--sgd-shadow-popovery 4 px de relleno..sgd-option: 40 px de alto, texto de menú (14 px), columna izquierda para la marca de selección (.sgd-option-check), resaltado--accentpara la opción activa (data-highlightedde Radix,data-activede los combobox propios,data-selectedde cmdk) o con foco, texto en 500 para la elegida, atenuada si está deshabilitada..sgd-option-flat(sin columna de marca) para acciones.ListboxStatus(searchable-combobox.tsx) +.sgd-listbox-status: «Buscando…», aviso previo, «Sin resultados.» y error (loadError, rojo).EntityComboboxahora muestra «No se pudieron cargar las opciones. Intenta de nuevo.» si la consulta falla (antes decía «Sin resultados.»).- Chevron único
expand_more(18 px, 60 % de opacidad) en Select, SearchableCombobox y MultiSelectCombobox (el combobox usabaunfold_more; el múltiple cambiaba aexpand_less).
Dónde se aplicó (un cambio en la primitiva alcanza a todos sus usos):
| Primitiva | Alcance |
|---|---|
ui/select.tsx (SelectContent/SelectItem) | los ~94 archivos con Select (filtros, diálogos, paginación…) y FloatingUnderlineSelect (el «Tipo» de la imagen) |
ui/dropdown-menu.tsx (contenedor, ítems, casillas y radios) | todos los menús de acciones: mismo aspecto, comportamiento propio (siguen siendo menús, no selectores) |
ui/command.tsx (CommandItem) | paleta/búsqueda global |
ui/searchable-combobox.tsx | EntityCombobox y *Combobox de entity-pickers, CitizenCombobox, LocationCombobox, FloatingSelectField, OptionCombobox |
ui/data-table/multi-select-combobox.tsx | filtros de tablas y, ahora, MultiSelectChecklist |
Implementaciones duplicadas sustituidas:
MultiSelectChecklist(botón +Commandcon buscador dentro del desplegable; 2 usos: grupos de destino y asignación masiva de motivos) ahora es un envoltorio deMultiSelectCombobox(se escribe en el campo, chips, fila «Seleccionar todos»), con la misma API.- Un
<select>nativo (inspections/inspection-document-list.tsx) pasó aSelect. - Las clases de los campos-combobox (con borde / subrayado) y el marco de etiqueta flotante + error estaban copiados en cada selector: ahora viven en
ui/option-combobox.tsx(COMBOBOX_BOXED_INPUT,COMBOBOX_UNDERLINE_INPUT,ComboboxFieldFrame,comboboxChevronClass). - Nuevo
OptionCombobox(ui/option-combobox.tsx): selector con búsqueda para listas estáticas, subrayado con etiqueta flotante (conlabel) o con borde. Es la vía para convertir unSelectde catálogo largo en uno con búsqueda.
Corrección (2026-10-08): FloatingUnderlineSelect (el «Tipo» de Destinatario, «Motivo de la firma» y «Agregar criterio» de los filtros) seguía siendo un botón Radix que no permitía escribir y se veía distinto de los demás selectores del formulario. Ahora es un envoltorio de OptionCombobox con la misma API (label, value, options, onValueChange, clearable, placeholder, error, disabled): se escribe en el campo, filtra sin distinguir tildes y usa la lista común. Con clearable añade la fila «Todos» (texto de placeholder) en lugar de la «X». Verificado en Chrome: clic abre la lista sin buscador interno, «ciud» filtra a «Ciudadano», ↓ + Enter elige y deja el valor.
Se mantienen las diferencias funcionales: selección simple corta = Select (Radix); simple con búsqueda = SearchableCombobox/OptionCombobox/EntityCombobox; múltiple = MultiSelectCombobox con chips; dependientes (p. ej. Personal según Dependencia) conservan sus parámetros; menús de acciones = DropdownMenu.
Muestra: /design-system/componentes → «Selectores desplegables» (simple subrayado y con borde, con búsqueda en ambos estilos, múltiple, menú de acciones y los tres estados de la lista).
Verificado en Chrome (2026-10-08): contenedor y filas idénticos en las seis variantes de la muestra; modo claro y oscuro (LATAM e Institucional oscuro); axe sin violaciones con la lista abierta; dentro de un modal a 700 × 520 px las listas no se cortan ni salen de la pantalla, Escape cierra solo la lista y un segundo Escape cierra el modal; elegir con teclado (↓, Enter) en simple, con búsqueda y múltiple actualiza el valor (ids correctos). tsc 0 errores, ESLint 0 errores (6 avisos previos ajenos), icons:check OK.
Pendientes reales:
- Catálogos largos con
Selectsimple. Siguen siendoSelectsin búsqueda (solo se unificó su lista y chevron): cambiarlos exige tocar cada pantalla y no se pueden probar sin sesión/backend. Son los de oficinas, usuarios, tipos de documento, cargos, grupos, calendarios, etc. en diálogos y filtros defeatures/y en Mesa de Partes (bandeja, referencias, destinatarios). Cada uno: sustituir porOptionCombobox(oEntityComboboxsi la lista viene de un endpoint paginado). - Los selectores con borde de los diálogos de administración mantienen etiqueta externa (el subrayado flotante se pidió para formularios de documentos y filtros).
- El campo con borde del combobox muestra, con foco, el contorno global de
inputademás del anillo (igual queInput); elSelectcon borde solo con teclado. Diferencia menor. - No probado: pantallas reales de administración y Mesa de Partes (sin sesión ni backend),
CitizenCombobox/LocationComboboxcontra el backend, lector de pantalla, Firefox/Safari.
Qué componentes lo usan
| Componente | Dónde | Notas |
|---|---|---|
EntityCombobox y todos los *Combobox de entity-pickers.tsx (Oficina, Usuario, Tipo de documento, Prioridad, Trámite, Organización, Ciudadano de catálogo, Grupo de destino, …) | src/components/admin/ | Consulta endpoint?q= con 300 ms de retraso; con label usa subrayado con etiqueta flotante, sin label un campo con borde (tablas compactas). Conserva params (p. ej. officeId de Personal según Dependencia), getLabel, getDescription, clearable, error, required |
CitizenCombobox | features/citizens/ | Busca desde 2 letras (DNI, nombres, apellidos) |
LocationCombobox | features/locations/ | Busca desde 2 letras; fila «Sin especificar» (campo opcional) |
FloatingSelectField | features/mesa-partes/components/shared/ | Filtra localmente; conserva su geometría de FloatingInput |
No se tocó MultiSelectCombobox/multi-select-checklist (selección múltiple con casillas): es otro patrón. searchPlaceholder de EntityCombobox quedó obsoleto e ignorado (ya no hay buscador dentro del desplegable).
Comportamiento
- Abrir: clic en el campo,
↓, o empezar a escribir. Al abrir se carga la primera página de opciones (onOpen). - Escribir filtra mientras se escribe: en consultas remotas, con
?q=(el llamador hace el retraso y las peticiones); en listas locales (filterLocally), sin distinguir mayúsculas ni tildes («direccion» encuentra «Dirección»;normalizeSearch). - El texto escrito es una búsqueda temporal, no un valor:
value/displayLabel(la selección confirmada) solo cambian al elegir una opción (clic o Enter). Escribir, abrir y cerrar sin elegir no cambian lo que se guarda (se comprobó con el payload de «Grabar»: tras escribir «carta» y cerrar,documentTypeIdsigue siendo el del tipo elegido). - Cerrar sin elegir (Escape, clic fuera, Tab): el campo recupera el nombre de la selección anterior, o queda vacío si no había.
- Al elegir se muestra el nombre en el campo y se conserva el id (
onChange(id)/onValueChange). Conclearable/clearLabel, una fila inicial («Sin selección») la quita (se oculta mientras se escribe). - Teclado:
↓/↑mueven la opción activa (aria-activedescendant),Enterelige la activa (solo si hay lista abierta; si no, el Enter sigue su curso),Escapecierra la lista sin cerrar el diálogo que la contiene,Tabcierra sin elegir.←,→, Inicio, Fin, Supr, copiar/pegar y demás conservan su función normal de edición (verificado:←mueve el cursor sin cambiar la opción activa). - Reemplazo al escribir: al enfocar o abrir con el clic el nombre actual queda seleccionado; lo primero que se teclea lo reemplaza, y si se teclea al final de un nombre ya elegido, solo cuenta lo nuevo como búsqueda.
- Estados de la lista: «Buscando…», aviso previo («Escribe al menos 2 letras…» en Ciudadano/Ubicación) y «Sin resultados.» Sin buscador adicional dentro del desplegable.
- Posicionamiento:
Popoverde Radix anclado al campo (PopoverAnchor), ancho del campo (mín. 14 rem),collisionPaddingde 8 px, alto limitado al espacio disponible; el foco no sale del campo al abrir ni al cerrar (elegir con el mouse no le quita el foco:onMouseDownevita el blur). - Estilo: subrayado con etiqueta flotante del DS (
.sgd-underline-input; la etiqueta queda siempre arriba y el texto de ayuda/valor debajo, así no se superpone al escribir ni al seleccionar) o campo con borde; chevron a la derecha reservado conpadding-right. - Accesibilidad:
role="combobox",aria-autocomplete="list",aria-expanded,aria-controls(listarole="listbox"), opcionesrole="option"conaria-selected, nombre poraria-labelledby(etiqueta) oaria-label, error conaria-invalid/aria-describedby.
API de SearchableCombobox
value, displayLabel (selección confirmada) · options ({ value, label, description? }), loading, hint, emptyMessage · filterLocally · onOpen, onQueryChange, onSelect(option | null) · clearLabel · placeholder, disabled · inputClassName, className, inputProps, trailing, chevron. Quien lo usa pone el estilo (subrayado, cuadro, etc.) y, si hay, la etiqueta y el error.
Verificación (2026-10-08, Chrome real)
«Nuevo documento» con la API simulada y FloatingSelectField en una página temporal (ya retirada): abrir con clic; sin buscador extra dentro de la lista (0 input); filtrado «resolucion» → «Resolución Directoral»; Enter; «Sin resultados.»; Escape y Tab restauran la selección anterior; ↓↓+Enter; ← sin cambiar la opción; selección con mouse; payload de «Grabar» con el tipo confirmado tras escribir sin elegir; Dependencia → Personal (users?officeId=o2) y filtro local con fila «sin selección»; axe sin violaciones con la lista abierta.
No verificado: CitizenCombobox y LocationCombobox (no hay backend/sesión: solo se comprobó que compilan, pasan lint y reutilizan la misma base); lector de pantalla; el desplegable dentro de diálogos reales (Escape cierra solo la lista por la pila de capas de Radix, no se probó en un diálogo); Firefox/Safari; el tooltip de texto recortado que tenía el selector con etiqueta se retiró (el campo muestra el texto completo al enfocarlo).