Modals — Unified Overlay System
Sistema de superposiciones headless (modal, pantalla completa, banner superior/inferior, panel lateral, toast) con elegibilidad (segmentación, ventana de fechas, prioridad), limitación de frecuencia en cliente, programación de disparadores y un patrón de proveedor (sin base de datos ni backend integrados). Adaptadores para React y Svelte, mobile-first y accesible.
Abrir playground · Modals en vivoVariables de entorno
Este componente no requiere variables de entorno.
Instalación manual
Descarga el tarball y copia a tu proyecto los archivos de tu framework:
curl -L -o modals.tar.gz https://modularcorehub.com/registry/modals.tar.gz
tar -xzf modals.tar.gz | Origen | Destino en tu proyecto |
|---|---|
core/types.ts | src/modularcore/modals/core/types.ts |
core/storage.ts | src/modularcore/modals/core/storage.ts |
core/frequency.ts | src/modularcore/modals/core/frequency.ts |
core/eligibility.ts | src/modularcore/modals/core/eligibility.ts |
core/provider.ts | src/modularcore/modals/core/provider.ts |
core/providers/in-memory.ts | src/modularcore/modals/core/providers/in-memory.ts |
core/triggers.ts | src/modularcore/modals/core/triggers.ts |
core/modals.ts | src/modularcore/modals/core/modals.ts |
adapters/react/use-modals.ts | src/modularcore/modals/adapters/react/use-modals.ts |
ui/a11y/focus-trap.ts | src/modularcore/modals/ui/a11y/focus-trap.ts |
ui/a11y/reduced-motion.ts | src/modularcore/modals/ui/a11y/reduced-motion.ts |
ui/safe/message.ts | src/modularcore/modals/ui/safe/message.ts |
ui/safe/url.ts | src/modularcore/modals/ui/safe/url.ts |
ui/safe/style.ts | src/modularcore/modals/ui/safe/style.ts |
ui/react/internal/safe-render.ts | src/modularcore/modals/ui/react/internal/safe-render.ts |
ui/react/internal/use-escape-key.ts | src/modularcore/modals/ui/react/internal/use-escape-key.ts |
ui/react/internal/OverlayBody.tsx | src/modularcore/modals/ui/react/internal/OverlayBody.tsx |
ui/react/ModalOverlay.tsx | src/modularcore/modals/ui/react/ModalOverlay.tsx |
ui/react/FullscreenOverlay.tsx | src/modularcore/modals/ui/react/FullscreenOverlay.tsx |
ui/react/TopBanner.tsx | src/modularcore/modals/ui/react/TopBanner.tsx |
ui/react/BottomBanner.tsx | src/modularcore/modals/ui/react/BottomBanner.tsx |
ui/react/SlideIn.tsx | src/modularcore/modals/ui/react/SlideIn.tsx |
ui/react/Toast.tsx | src/modularcore/modals/ui/react/Toast.tsx |
ui/react/ModalsRenderer.tsx | src/modularcore/modals/ui/react/ModalsRenderer.tsx |
ui/vanilla-styles.css | src/modularcore/modals/ui/vanilla-styles.css |
Instalación con CLI
modularcore add modalsDocumentación
@modularcore/modals es un sistema de overlays headless y unificado — modal, fullscreen, banner superior, banner inferior, slide-in y toast — con elegibilidad (targeting, ventana de fechas, prioridad), limitación de frecuencia en cliente, programación de triggers y un patrón de proveedor (sin base de datos ni backend incluidos). Incluye adaptadores para React y Svelte 5, mobile-first y accesible.
Sin base de datos, sin backend incluido
ModalsProvider (ver core/provider.ts) es el único punto de contacto entre el core y cualquier fuente de datos: getActiveModals(ctx) devuelve candidatos en bruto, y trackView/ trackInteraction son hooks que el consumidor conecta a su propio backend si quiere analítica persistida. Este paquete incluye core/providers/in-memory.ts como implementación de referencia y nada más — consulta docs/prisma-tracking-endpoint-example.md (dentro de packages/modals/) para ver cómo conectar un backend real detrás de la misma interfaz.
Qué incluye el paquete
core/types.ts— el modeloModalConfigagnóstico de framework (6 tipos de overlay, triggers, frecuencia, targeting, botones).core/eligibility.ts— filtro puro:isActive→ ventana de fechas → targeting → frecuencia.core/frequency.ts+core/storage.ts— limitación de frecuencia en cliente (always/once-per-session/once-per-day/once-ever) a través desessionStorage/localStorageinyectables.core/triggers.ts— programación de triggers (page-load/delay,scroll,exit-intent,click,manual) contra unTriggerEnvironmentinyectable.core/modals.ts—OverlayManager, el orquestador headless: resuelve las configuraciones elegibles en un ganador por slot singleton (modal/fullscreencomparten slot) más una pila detoastcon límite, programa triggers y muestra/descarta notificando a los suscriptores.core/provider.ts,core/providers/in-memory.ts— el punto de contacto de proveedor + su implementación de referencia.adapters/react,adapters/svelte— bindings finos sobreOverlayManager(el adaptador de Svelte usa runas de Svelte 5). Una instancia de manager por hook/runa, condestroy()conectado al desmontaje para que los listeners de scroll/mouseout/timeout siempre se limpien.ui/a11y/*,ui/safe/*— helpers compartidos y agnósticos de framework de focus-trap/reduced- motion y validación de URL/color, consumidos tanto porui/reactcomo porui/svelte.ui/react/*,ui/svelte/*— un conjunto de componentes por tipo de overlay, másModalsRenderer, que mapea el estado del manager a ellos.
Frontera de seguridad: el proveedor es contenido no confiable
Los resultados de getActiveModals() son contenido que se renderiza en el DOM. message, imageUrl y el url de los botones pueden proceder de una base de datos o CMS con menor confianza que el propio código que llama a este paquete — así que ui/react/ui/svelte tratan cada ModalConfig como no confiable y aplican esta frontera ellos mismos (no se delega al consumidor):
messagese renderiza como texto plano por defecto. Solo cuandoallowHtml: truepasa porrenderMarkdownToHtml(de@modularcore/ai-chat/markdown, solo Markdown — nunca HTML crudo).- El
urlde los botones yimageUrlpasan por un allowlist (ui/safe/url.ts):https:/http:/mailto:/tel:para enlaces,https:(odata:opcional con límite de tamaño) para imágenes.javascript:y cualquier otra cosa se descarta, cayendo a un<button>plano o sin imagen en lugar de la URL suministrada. - Los enlaces externos siempre llevan
rel="noopener noreferrer"; las imágenes,referrerpolicy="no-referrer". bgColor/textColorse validan contra un patrón hex/rgb()/rgba()(ui/safe/style.ts) antes de llegar nunca a un atributostyle;maxWidthes un enum cerrado mapeado a una clase, nunca una cadena de estilo libre.
Uso básico (React)
import { useModals } from '@modularcore/modals/react';
import { ModalsRenderer } from '@modularcore/modals/ui/react/ModalsRenderer';
import { createInMemoryProvider } from '@modularcore/modals/providers/in-memory';
const provider = createInMemoryProvider({
modals: [
{
id: 'welcome',
type: 'top-banner',
message: '¡Bienvenido! 20% de descuento hoy.',
trigger: { type: 'delay', value: 2000 },
},
],
});
function App() {
const { state, dismiss } = useModals(provider, { path: window.location.pathname });
return <ModalsRenderer state={state} onDismiss={dismiss} />;
}
Uso básico (Svelte 5)
<script lang="ts">
import { createModals } from '@modularcore/modals/svelte';
import ModalsRenderer from '@modularcore/modals/ui/svelte/ModalsRenderer.svelte';
import { createInMemoryProvider } from '@modularcore/modals/providers/in-memory';
const provider = createInMemoryProvider({ modals: [/* ... */] });
const modals = createModals(provider, { path: '/' }); // debe llamarse durante la inicialización del componente
</script>
<ModalsRenderer state={modals.state} ondismiss={modals.dismiss} />
Accesibilidad y diseño responsive
modal/fullscreen atrapan el foco (ciclo Tab/Shift+Tab, foco restaurado al cerrar), se cierran con Escape y exponen role="dialog" aria-modal="true". top-banner/bottom-banner/slide-in/ toast usan aria-live="polite" (toast: role="status") y nunca roban el foco. Todos los tipos son mobile-first: ancho completo o casi completo en viewports estrechos, sin scroll horizontal, y las transiciones se omiten cuando prefers-reduced-motion está activo.
Prueba este componente en vivo en el Playground de Modals.