--- title: Eidos Theming — Guías de autoría type: guide audience: human + agent status: current source: extracted from src/uix/eidos/THEMING.md (was §8 + §9) --- # Eidos Theming — Guías de autoría > Las dos guías how-to del theming de eidos: añadir un componente nuevo y > definir un theme. Extraídas de [`THEMING.md`](./THEMING.md) (referencia E1) > para vivir como guía E4. El contrato de tokens que sostiene todo esto es > [`TSC.md`](./TSC.md). --- ## Cómo añadir un componente nuevo Asumiendo que ya tienes el morfo, soma y el scaffolding del wrapper eidos (`src/uix/eidos/components/{name}/`): ### Paso 1 — Decide qué tokens necesitas Mira componentes similares (`button`, `toggle`, `switch`). Identifica qué dimensiones tu componente expone: - ¿Tiene `data-color`? → palette tokens. - ¿Tiene `data-variant`? → variant tokens. - ¿Tiene `data-size`? → size tokens. - ¿Cuántas partes tiene? → tokens por part. ### Paso 2 — Añade el recipe en `lib/recipes/base.ts` ```ts // Within THEME_BASE_RECIPE_TOKENS: 'my-component': { // size tokens — scope 'root' (estables) 'height-md': '36px', 'padding-inline-md': 'var(--space-3)', 'gap': 'var(--space-2)', 'radius': 'var(--radius-md)', // per-color literal definitions — scope 'root' 'primary-solid': 'var(--color-primary-solid)', 'affirm-solid': 'var(--color-affirm-solid)', 'threat-solid': 'var(--color-threat-solid)', // palette dinámica — scope 'host' default + overrides por color 'palette-solid': { declarations: [ { value: 'var(--my-component-primary-solid)', scope: 'host' }, { value: 'var(--my-component-affirm-solid)', scope: 'color:affirm' }, { value: 'var(--my-component-threat-solid)', scope: 'color:threat' } ] }, // tokens derivados — scope 'host' (deps inferidas) 'solid-bg': { value: 'var(--my-component-palette-solid)', scope: 'host' } } ``` ### Paso 3 — Regenera ```bash npm run generate:eidos-css ``` Si tu config viola TSC, te avisa al regenerar: ``` Eidos recipe scope contract violations: - my-component.solid-bg (scope root): dependency 'palette-solid' is only declared at scopes [host], none of which is reachable from the consumer's scope. ``` ### Paso 4 — Escribe el recipe CSS `src/uix/eidos/components/my-component/my-component.css`: ```css [data-my-component] { /* Private tokens — sólo este recipe los lee */ --_my-component-bg: var(--my-component-solid-bg); --_my-component-radius: var(--my-component-radius); display: inline-flex; align-items: center; padding-inline: var(--my-component-padding-inline-md); height: var(--my-component-height-md); border-radius: var(--_my-component-radius); background: var(--_my-component-bg); gap: var(--my-component-gap); } [data-my-component][data-disabled] { opacity: var(--opacity-disabled); pointer-events: none; } ``` ### Paso 5 — Importa en `index.css` ```css @import './components/my-component/my-component.css'; ``` ### Paso 6 — Verifica ```bash npm run generate:eidos-css npm test -- src/uix/eidos npm run morfo:check node --import tsx/esm scripts/eidos-lint.ts my-component ``` ### Anti-pattern común: declarar tokens compuestos en `:root` ```ts // ❌ INCORRECTO — TSC fallará 'my-component': { 'palette-solid': { value: 'var(--color-neutral-solid)', scope: 'host' }, 'solid-bg': 'var(--my-component-palette-solid)' // ← scope 'root' implícito, deps en 'host' } // ✓ CORRECTO 'my-component': { 'palette-solid': { value: 'var(--color-neutral-solid)', scope: 'host' }, 'solid-bg': { value: 'var(--my-component-palette-solid)', scope: 'host' } } ``` --- ## Cómo definir un theme Eidos soporta **3 modos** de definir un theme: ### Modo 1 — Patch del theme base (recomendado) Cambia sólo lo que necesitas; el resto sigue el base: ```ts import { ActiveEidos } from '$uix/eidos'; ActiveEidos.create({ themeBase: { semantics: { color: { roles: { primary: 'blue', // primary usa la escala blue Radix secondary: 'plum' } } }, primitives: { typography: { families: { primary: { family: 'Inter' } } } } }, applyDom: true }); ``` ### Modo 2 — Config completa Si quieres autoría desde cero: ```ts import { ActiveEidos, defineEidosConfig } from '$uix/eidos'; const config = defineEidosConfig({ primitives: { /* … */ }, semantics: { color: { /* … */ } }, themes: { /* … */ } }); ActiveEidos.create({ config, applyDom: true }); ``` ### Modo 3 — Theme CSS-only (sin TypeScript) Eidos publica el contrato como CSS vacío para que externals lo sobrescriban: ```ts const contract = activeEidos.renderContractCss({ themeSelector: "[data-theme='acme-light']" }); // Output: // [data-theme='acme-light'] { // --scale-blue-9: ; // --color-primary-solid: ; // --size-md-control-height: ; // ... // } ``` El consumer rellena los valores: ```css [data-theme='acme-light'] { --scale-blue-9: #006adc; --color-primary-solid: var(--scale-blue-9); --size-md-control-height: 38px; } ``` Y carga ese CSS junto con el de Eidos. Con `themeSource: 'css'`, `ActiveEidos` no genera theme propio. ### Persistencia versionada ```ts const document = activeEidos.toDocument(); // → { kind: 'uix.eidos-config', version: 1, options: {...} } const json = activeEidos.serialize(); localStorage.setItem('user-theme', json); // Más tarde const hydrated = createActiveEidos({ config: JSON.parse(localStorage.getItem('user-theme')!), prefs, dom }); ``` El document envelope tiene `version` para migraciones futuras. ---