@nexa/ui — Interactivos, sin isla
Estado real en cliente — pero ninguno necesita una isla: activación progresiva de siempre.
Dialog
function open() { ui.openDialog(document.querySelector(".nx-dialog")); }
function close() { ui.closeDialog(document.querySelector(".nx-dialog")); }
<button onClick={open}>Abrir</button>
<div class="nx-dialog" hidden>
<p>Contenido</p>
<button onClick={close}>Cerrar</button>
</div>ui.openDialog(el)/ui.closeDialog(el) — foco atrapado, Escape cierra, devuelve el foco al elemento anterior. ui está siempre disponible, no hace falta declararlo en [imports].
Apilamiento de Dialog
// abrir un segundo Dialog desde dentro del primero ya abierto:
// se apila sobre él sin romper ninguno de los dosAbrir un Dialog mientras otro ya está abierto lo apila encima (z-index correcto) y oculta el de abajo de lectores de pantalla (aria-hidden) mientras esté cubierto.
ui.confirm() / ui.alert()
async function removeItem() {
if (await ui.confirm("¿Seguro que querés borrar esto?")) {
// borrar
}
}
await ui.alert("Guardado con éxito");Confirmar antes de una acción destructiva, o avisar que algo se guardó, sin escribir el <div class="nx-dialog"> a mano. Escape en confirm() cuenta como cancelar; en alert(), como aceptar.
Drawer
function openMenu() { ui.openDrawer(document.querySelector(".nx-drawer")); }
<button onClick={openMenu}>Menú</button>
<div class="nx-drawer" hidden>
<a href="/">Inicio</a>
</div>Mismo contrato de accesibilidad que Dialog, pensado para sidebars/menús laterales en vez de una caja centrada.
Toast / snackbar
function saveOk() {
ui.notify({ message: "Guardado con éxito", variant: "success" });
}ui.notify() — no confundir con platform.notify() (notificación real del sistema operativo): esta es efímera, dentro de la página. Se apila con otras, se auto-cierra a los 4s.
Tabs
function selectTab(event) { ui.selectTab(event); }
<div class="nx-tab-group">
<div class="nx-tab-list" role="tablist">
<button class="nx-tab" data-tab="uno" aria-selected="true" onClick={selectTab}>Uno</button>
<button class="nx-tab" data-tab="dos" onClick={selectTab}>Dos</button>
</div>
<div class="nx-tab-panel" data-tab="uno">Contenido uno</div>
<div class="nx-tab-panel" data-tab="dos" hidden>Contenido dos</div>
</div>ui.selectTab(event) lee de qué botón vino el click y su data-tab, encuentra el .nx-tab-group más cercano, y hace el resto.
Accordion
function toggle(event) { ui.toggleAccordionItem(event); }
<div class="nx-accordion">
<div class="nx-accordion-item">
<button class="nx-accordion-trigger" aria-expanded="false" onClick={toggle}>Pregunta</button>
<div class="nx-accordion-panel" hidden>Respuesta</div>
</div>
</div>ui.toggleAccordionItem(event). Con data-exclusive="true" en el contenedor, abrir un item cierra los demás.
Dropdown
function toggleMenu(event) { ui.toggleDropdown(event); }
function pick(event) { ui.selectDropdownOption(event); }
<div class="nx-dropdown">
<button class="nx-dropdown-trigger" aria-expanded="false" onClick={toggleMenu}>Elegir…</button>
<ul class="nx-dropdown-menu" role="listbox" hidden>
<li class="nx-dropdown-option" data-value="m" onClick={pick}>M</li>
</ul>
</div>ui.toggleDropdown(event) / ui.selectDropdownOption(event) — se cierra con click afuera o Escape, sin atrapar el foco (un dropdown no bloquea el resto de la página).
Ordenar y filtrar una Table
function sortByColumn(event) { ui.sortTable(event); }
function filterOrders(event) { ui.filterTable(document.getElementById("orders"), event.target.value); }
<input type="search" onInput={filterOrders} />
<table id="orders" class="nx-table">
<thead><tr><th data-sortable onClick={sortByColumn}>Cliente</th></tr></thead>
<tbody>...</tbody>
</table>ui.sortTable(event) ordena por la columna del <th> clickeado (numérico si ambas celdas lo son). ui.filterTable(table, query) oculta filas cuyo texto no matchea. Ambos client-side, sin re-pedir nada al backend.
Buscador de página reactivo
function onSearchInput(event) {
const input = event.currentTarget;
input.pageSearch ??= ui.createPageSearch(document.getElementById("catalogo"));
input.pageSearch.setQuery(event.target.value);
}
<input type="search" onInput={onSearchInput} />
<div id="catalogo">
<article data-searchable>Zapatillas Nezha</article>
</div>ui.createPageSearch(container) filtra elementos data-searchable con un Signal real de @nexa/reactivity por debajo. El controlador se cuelga del propio <input> porque cada chunk es un módulo aislado.