5.6 KiB
| title | type | audience | status | source |
|---|---|---|---|---|
| Eidos Theming — Guías de autoría | guide | human + agent | current | 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(referencia E1) para vivir como guía E4. El contrato de tokens que sostiene todo esto esTSC.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
// 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
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:
[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
@import './components/my-component/my-component.css';
Paso 6 — Verifica
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
// ❌ 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:
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:
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:
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:
[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
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.