soul v4.2.0

@jun/soul/ui

UI

Shell HTML, theme completo y escapado automatico.

El modelo#

soul emite esqueleto semantico —clases .soul-btn, .soul-card, .soul-table…— sin estilo propio, y todo el diseño sale del theme. Cada proyecto controla el aspecto completo, incluido el panel de administracion, cambiando tokens.

Crear el shell#

TypeScript
import { createShell } from "@jun/soul/ui";

export const { shell, errorPage } = createShell({
  siteName: "Mi SaaS",
  theme: {
    primary: "#2f6f4f",
    bg: "#f7faf8",
    fontDisplay: "'Fraunces', serif",
  },
});
shell({ title, body, nav? })
Documento HTML completo. body y nav se pasan por raw().
errorPage(titulo, mensaje)
Pagina de error ya estilizada.

El theme#

SoulTheme tiene unos 55 tokens y se pasa parcial: lo que no toques queda con el valor por defecto (marfil y dorado, Fraunces).

GrupoTokens
Superficiesbg, surface, surfaceAlt, border, borderStrong
Textotext, textMuted, textFaint, textInverse
Acentosprimary, primaryHover, onPrimary, danger*, success*, warning*, link, linkHover
Barra lateralsidebarBg, sidebarText, sidebarActiveBg, sidebarBrand
TipografiafontBody, fontDisplay, fontMono, fontSizeXs…3xl, weight*, headingWeight
Forma y espacioradius, radiusSm/Lg/Pill, controlHeight, gap
LayoutsidebarWidth, contentMaxWidth, contentPadX/Y
EfectosshadowSm, shadow, cardBorder, cardShadow

Control total con CSS propio#

El css que pases se inyecta despues de la hoja de soul, asi que sobreescribe cualquier .soul-*. Es la valvula de escape para el detalle fino.

TypeScript
export const { shell } = createShell({
  siteName: "Mi SaaS",
  theme: { primary: "#b4452f" },
  css: `
    .soul-btn { text-transform: uppercase; letter-spacing: .06em; }
    .soul-card { border-radius: 2px; }
  `,
});

Piezas de layout#

sidebar({ siteName, sections, userInitial, userName, userEmail, logoutHref? })
Barra lateral con secciones. Un item con href: null queda inerte (util para lo que aun no existe).
shellLayout({ sidebar, content })
Combina barra lateral y contenido.
ui.{input,select,label,btnPrimary,btnSecondary,btnDanger,card,link}
Nombres de clase .soul-*, no funciones.
icons.{grid,shield,users,plus,upload,trash,close,qr,settings,billing,help…}
Iconos SVG en linea.
logoMark(), DEFAULT_FONT_LINKS, AJAX_PANELS_SCRIPT
Extras.

Cabeceras de seguridad#

TypeScript
import { securityHeaders } from "@jun/soul/ui";

app.use("*", securityHeaders({
  tailwindCdn: true,          // solo si cargas Tailwind por CDN
  turnstile: true,            // si usas el captcha
  extraImgSrc: ["https://cdn.midominio.com"],
}));

Pone CSP, X-Frame-Options, X-Content-Type-Options y Referrer-Policy. Si solo quieres la cadena de CSP, defaultCsp(config) la arma sin el middleware.

Escapado automatico#

Todo el modulo esta escrito con html\`\`: cualquier \${valor} interpolado se escapa solo, como en JSX. Para meter markup de confianza se usa raw().

TypeScript
import { html, raw } from "@jun/soul/ui";

// Seguro: el nombre se escapa aunque traiga <script>
const saludo = html`<p>Hola \${usuario.nombre}</p>`;

// raw() SOLO para markup propio, jamas para datos del usuario
const pagina = html`<div>\${raw(saludo)}</div>`;