Hooks
La librería exporta 18 hooks. Casi todos son las piezas con las que están construidos sus propios componentes, y se publican porque hacen falta igual cuando montas algo a medida.
Todos salen de la entrada principal:
import { useToast, useDebouncedValue, useMediaQuery } from '@openfactu/ui';Avisos y diálogos
Sección titulada «Avisos y diálogos»useToast
Sección titulada «useToast»Los avisos. Necesita el ToastProvider montado en la raíz. La API completa está en
Feedback y estado.
const toast = useToast();
toast.success('Factura guardada');toast.promise(guardar(), { loading: 'Guardando…', success: 'Guardado', error: 'Falló' });usePopup
Sección titulada «usePopup»Diálogos por promesa, sin montar componentes ni llevar estado. Necesita el
PopupProvider. Detalle en Overlays.
const popup = usePopup();
if (await popup.confirm({ message: '¿Anular la factura?', tone: 'danger' })) { await anular();}useCommandPalette
Sección titulada «useCommandPalette»Estado y atajo global del buscador. Ver Navegación.
const paleta = useCommandPalette(); // Ctrl/⌘ + K<SearchTrigger onOpen={paleta.openPalette} /><CommandPalette open={paleta.open} onClose={paleta.close} sections={secciones} />useContextMenu
Sección titulada «useContextMenu»Menú del click derecho. Ver Botones y acciones.
const { contextMenu, openContextMenu } = useContextMenu();
<tr onContextMenu={(e) => openContextMenu(e, ACCIONES)}>…</tr>{contextMenu}useTheme
Sección titulada «useTheme»El tema actual y cómo cambiarlo. Lanza si no hay ThemeProvider por encima; si no
te importa que falte, usa useThemeOptional.
const { theme, mode, toggleMode, applyPreset, activePresetId, cssVars } = useTheme();
<Switch checked={mode === 'dark'} onChange={toggleMode} label="Modo oscuro" />| Devuelve | Tipo | Descripción |
|---|---|---|
theme |
ResolvedTheme |
El tema completo, ya derivado |
setTheme |
(patch | (prev) => patch) => void |
Cambia el tema |
mode |
'light' | 'dark' |
Modo actual |
setMode |
(mode: ThemeMode) => void |
Cambia el modo |
toggleMode |
() => void |
Alterna claro/oscuro |
applyPreset |
(presetId: string) => void |
Aplica un preset del catálogo |
activePresetId |
string | null |
Preset activo, o null si el tema es a medida |
cssVars |
Record<string, string> |
Mapa --variable → valor del tema actual |
useThemePresets
Sección titulada «useThemePresets»El catálogo de temas: los de fábrica y los que hayan registrado los plugins. Se resuscribe al registro, así que un selector construido con esto se repinta solo en cuanto un plugin aporta o retira uno.
const presets = useThemePresets();
<div className="grid grid-cols-3 gap-2"> {presets.map((p) => ( <button key={p.id} onClick={() => applyPreset(p.id)}>{p.label}</button> ))}</div>Más en Temas.
useColorScheme
Sección titulada «useColorScheme»El modo activo leído de la clase dark del documento, observando el atributo class
de la raíz: reacciona al cambio de tema sin recargar.
const modo = useColorScheme(); // 'light' | 'dark'const color = seriesColor(0, modo);Acepta un elemento como argumento, para leer el modo de un subárbol tematizado aparte.
Datos y entrada
Sección titulada «Datos y entrada»useDebouncedValue
Sección titulada «useDebouncedValue»Devuelve el valor una vez ha dejado de cambiar durante delay milisegundos. Es lo que
evita disparar una búsqueda en cada tecla.
const [texto, setTexto] = React.useState('');const consulta = useDebouncedValue(texto, 300);
React.useEffect(() => { buscar(consulta); }, [consulta]);delay vale 250 por defecto; con 0 o menos devuelve el valor sin esperar.
useInfiniteScroll
Sección titulada «useInfiniteScroll»Carga la página siguiente cuando el final de la lista se acerca a la pantalla.
const { sentinelRef } = useInfiniteScroll({ hasMore, onLoadMore: traerMas, loading });
<tbody> {filas.map(pintar)} <tr ref={sentinelRef} aria-hidden /></tbody>Usa IntersectionObserver en lugar de la posición de scroll: medir scrollTop obliga a
escuchar cada evento, falla dentro de contenedores anidados y no sabe si el final está
realmente visible.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
hasMore |
boolean |
— | Quedan más páginas. Con false el observador se apaga |
onLoadMore |
() => void |
— | Se llama una vez por cada entrada del centinela |
loading |
boolean |
— | Hay una petición en vuelo: no se pide otra |
disabled |
boolean |
— | Apaga el mecanismo (por ejemplo mientras se filtra) |
rootMargin |
string |
'200px' |
Cuánto antes del final se dispara |
root |
RefObject<HTMLElement> |
el viewport | Contenedor con scroll propio |
Devuelve { sentinelRef }, que vale para un <tr>, un <li> o un <div>.
Table y List ya lo llevan dentro con su prop infinite; este hook es para listas
que montes tú.
Medios y movimiento
Sección titulada «Medios y movimiento»useMediaQuery
Sección titulada «useMediaQuery»Sigue una media query del navegador. Usa useSyncExternalStore, así que el primer
render en servidor devuelve el valor de reserva y no hay desajuste al hidratar.
const esAncho = useMediaQuery('(min-width: 1024px)');const apaisado = useMediaQuery('(orientation: landscape)', true);usePrefersReducedMotion
Sección titulada «usePrefersReducedMotion»true cuando el sistema pide reducir el movimiento.
const sinMovimiento = usePrefersReducedMotion();
<Chart type="line" animate={!sinMovimiento} … />Superposiciones y accesibilidad
Sección titulada «Superposiciones y accesibilidad»Los cuatro con los que están construidos Modal, Drawer, DropdownMenu y compañía.
usePopover
Sección titulada «usePopover»Posiciona un popover portaleado a document.body con position: fixed: coordenadas de
viewport, volteo automático cuando no cabe, recorte contra los bordes, reposición al
hacer scroll o redimensionar, click fuera y Escape.
const { anchorRef, popoverRef, style, placement, ready } = usePopover<HTMLButtonElement>({ open: abierto, onClose: cerrar, preferredPlacement: 'bottom', matchAnchorWidth: true,});
<button ref={anchorRef} onClick={abrir}>Abrir</button>{abierto && createPortal( <div ref={popoverRef} style={style} className={ready ? '' : 'invisible'}>…</div>, document.body,)}| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
open |
boolean |
— | Abierto |
onClose |
() => void |
— | Cerrar |
preferredPlacement |
'top' | 'bottom' | 'left' | 'right' |
'bottom' |
Lado preferido; voltea si no hay espacio |
align |
'start' | 'end' |
'start' |
Alineación sobre el eje transversal |
offset |
number |
4 |
Separación en píxeles |
matchAnchorWidth |
boolean |
— | El popover toma el ancho del ancla (selects) |
minWidth |
number |
— | Ancho mínimo |
estimatedMaxHeight |
number |
320 |
Altura estimada para decidir el volteo antes de medir |
scrollStrategy |
'reposition' | 'close' |
'reposition' |
reposition para selects, close para menús |
closeOnEscape |
boolean |
true |
Cerrar con Escape |
Devuelve { anchorRef, popoverRef, style, placement, ready, update }. update()
recalcula la posición a mano, por si cambia el contenido.
useAnimatedPresence
Sección titulada «useAnimatedPresence»Mantiene un elemento montado durante la animación de salida y retrasa el estado
«visible» un fotograma tras montarlo, para que la transición de entrada se dispare. Es
el patrón que usan Drawer y Transition.
const { mounted, visible, duration } = useAnimatedPresence({ open: abierto });
if (!mounted) return null;return ( <div style={{ transitionDuration: `${duration}ms` }} className={visible ? 'opacity-100' : 'opacity-0'} > … </div>);duration vale 300 por defecto y se devuelve para que puedas sincronizar el
transition-duration en línea.
useScrollLock
Sección titulada «useScrollLock»Bloquea el scroll de la página mientras haya algún overlay abierto.
useScrollLock(abierto);Lleva un contador global: con dos capas superpuestas —un diálogo abierto desde
otro— cerrar la de arriba ya no desbloquea el scroll de la de abajo, que es justo lo que
pasa cuando cada componente escribe body.style.overflow por su cuenta. También
compensa el ancho de la barra de desplazamiento, para que el contenido no dé un salto
horizontal al abrirse el overlay.
useFocusTrap
Sección titulada «useFocusTrap»Retiene el foco dentro de un contenedor: al llegar al último elemento, Tab vuelve al primero, y Shift+Tab al revés. Al cerrar devuelve el foco a donde estaba, de modo que quien abrió el diálogo con el teclado no acaba al principio de la página.
const panelRef = React.useRef<HTMLDivElement>(null);
useFocusTrap(panelRef, { enabled: abierto, initialFocusRef: campoRef, preferredRegionSelector: 'form',});| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
enabled |
boolean |
— | Activa la retención |
initialFocusRef |
RefObject<HTMLElement> |
el primero enfocable | Qué recibe el foco al abrir |
restoreFocus |
boolean |
true |
Devuelve el foco al cerrar |
preferredRegionSelector |
string |
— | Zona preferente para el foco inicial |
Referencia rápida
Sección titulada «Referencia rápida»Los tres restantes son variantes de otros, sin API propia que explicar.
| Hook | Firma | Para qué |
|---|---|---|
useIsNarrow |
(maxWidth = 640) => boolean |
true por debajo del ancho indicado. Atajo de useMediaQuery |
useThemeOptional |
() => ThemeContextValue | null |
Como useTheme, pero devuelve null en lugar de lanzar cuando no hay proveedor |
useUiTheme |
() => ThemeContextValue |
Alias de useTheme con prefijo, para aplicaciones que ya tienen su propio useTheme y necesitan importar los dos en el mismo fichero durante una migración |
UiThemeProvider es el alias equivalente de ThemeProvider, por el mismo motivo.