You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
5.6 KiB
242 lines
5.6 KiB
|
4 months ago
|
---
|
||
|
|
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.
|
||
|
|
|
||
|
|
---
|
||
|
|
|