|
|
# Tema · Untitled UI · Carbon
|
|
|
|
|
|
Demo de theming en vivo para activeUIX sobre el sistema de Eidos. Es a la vez
|
|
|
un **tema completo** (`untitled-ui-light` / `untitled-ui-dark`) y un **playground**
|
|
|
que muestra todos los elementos del tema: roles de color, rampas físicas de 12
|
|
|
pasos, superficies, tipografía, espaciado, radios, sombras, tamaños y el aspecto
|
|
|
real de cada control con sus variantes — más la capa semántica (sema).
|
|
|
|
|
|
Ruta: `/temas/untitled-ui`.
|
|
|
|
|
|
## Identidad del tema
|
|
|
|
|
|
Fiel a [untitledui.com](https://untitledui.com) (tipografía Inter, sombras en
|
|
|
capas, radios de 8px, anillos de foco suaves) pero con una decisión deliberada:
|
|
|
el **primario es carbón** (grafito casi negro `#1F2024`), monocromo al estilo
|
|
|
Vercel / Linear — **no** el violeta de marca de UUI. El violeta de UUI (`#7F56D9`)
|
|
|
se reubica como **secundario**.
|
|
|
|
|
|
Los 9 roles canónicos del framework quedan retintados con jerarquía diferenciada
|
|
|
(ninguno se repite):
|
|
|
|
|
|
| Rol | Escala | Color | Papel |
|
|
|
| --- | --- | --- | --- |
|
|
|
| `primary` | carbon | `#1F2024` | Acción principal, monocroma. Texto auto-invertido vía `slots.contrast: '1'`. |
|
|
|
| `secondary` | violet | `#7F56D9` | Violeta de marca UUI. |
|
|
|
| `tertiary` | blue | `#1570EF` | Acento azul. |
|
|
|
| `neutral` | gray | `#667085` | Gris frío UUI. |
|
|
|
| `affirm` | teal | — | Positivo ligero (baja activación). |
|
|
|
| `fulfill` | green | — | Éxito / completado. |
|
|
|
| `risk` | amber | `#DC6803` | Aviso. |
|
|
|
| `threat` | red | `#D92D20` | Error / peligro. |
|
|
|
| `loss` | plum | `#7A3AAD` | Pérdida grave: violeta sombrío, posterior (más oscuro que el secundario). |
|
|
|
|
|
|
Otros tokens del tema:
|
|
|
|
|
|
- **Tipografía**: Inter (`primary` / `secondary` / `display`), Roboto Mono (`mono`).
|
|
|
Letter-spacing negativo en los displays (`hero` / `h1` / `h2` a `-0.02em`, `h3` a `-0.01em`).
|
|
|
- **Radios**: escala con `md = 8px` como base UUI (`none` → `full`).
|
|
|
- **Foco**: `focusRing` con `offset 1px` + `width 3px`; el halo lavanda se pinta
|
|
|
en `untitled-ui.css` (`--uui-ring`, derivado de `--color-secondary-solid`).
|
|
|
- **Sombras**: set en capas estilo UUI (light + dark).
|
|
|
- **Densidad**: `compact` / `comfortable` / `spacious` — **funcional**, escala el
|
|
|
espaciado y la altura de los controles (ver «Correcciones del engine» abajo).
|
|
|
|
|
|
El tema vive en `_lib/untitled-ui.ts` como un `EidosConfig` independiente
|
|
|
(no parchea el `base`): define escalas light/dark, el `ColorRoleMap` y los dos
|
|
|
themes. No inventa variants ni roles nuevos — solo retinta lo canónico.
|
|
|
|
|
|
## Componentes mostrados
|
|
|
|
|
|
Foundations (markup propio sobre tokens del tema): roles + rampas + superficies,
|
|
|
tipografía, espaciado / radio / sombra / tamaño.
|
|
|
|
|
|
Controles eidos reales, todos dirigidos por el tema:
|
|
|
|
|
|
- **Button** (6 variants × roles × tamaños × estados)
|
|
|
- **Badge**
|
|
|
- **Form**: Field, Select, Checkbox, RadioGroup, Switch, Slider
|
|
|
- **Feedback**: Banner, Progress, Avatar, Card
|
|
|
- **Tabs**, **Accordion**
|
|
|
- **Toggle**, **ToggleGroup** (single + multiple, segmentado)
|
|
|
- **Toolbar**
|
|
|
- **Overlays**: Popover, Dialog (portaleados)
|
|
|
- **Menús**: Menubar, DropdownMenu, ContextMenu, Tooltip (portaleados)
|
|
|
- **Semántica**: la capa sema (sonido + haptic + reacción visual `data-event-*`)
|
|
|
encendida en el layout vía `createActiveUix({ events: { sound, haptic, components } })`.
|
|
|
|
|
|
## Estructura
|
|
|
|
|
|
| Archivo | Rol |
|
|
|
| --- | --- |
|
|
|
| `_lib/untitled-ui.ts` | Config del tema (`EidosConfig`): escalas, roles, themes light/dark. Exporta `THEME_ROLES`, `THEME_SCALES`, `COLOR_STEPS`. |
|
|
|
| `+layout@.svelte` | Raíz de composición. Arranca `ActiveUix` + `Soma` + `ActiveEidos` con el tema. Posee modo (claro/oscuro) y densidad como fuentes visuales. El `@` resetea la herencia del chrome de `/uix`. |
|
|
|
| `+page.svelte` | La página demo. |
|
|
|
| `theme.css` | Chrome del shell + helpers de layout (`[data-temas-*]`). |
|
|
|
| `untitled-ui.css` | Capa de pulido scoped a `[data-theme^='untitled-ui']`: anillo lavanda, sombras de control, bordes. |
|
|
|
|
|
|
## Correcciones del engine de theming (dependencias)
|
|
|
|
|
|
Este tema fue el primer consumidor que reveló dos bugs del engine de Eidos,
|
|
|
corregidos **a nivel engine** (sin deuda en el tema). Documentados en detalle en
|
|
|
[`src/uix/eidos/THEMING.md` §20](../../../../src/uix/eidos/THEMING.md):
|
|
|
|
|
|
1. **Densidad inerte** — `data-density` no afectaba a nada. Fix: `renderStaticCss`
|
|
|
emite `space` y `control-height` como `calc(value * var(--density-*-scale))`,
|
|
|
de modo que `[data-density]` reescala de verdad. (§20.1)
|
|
|
2. **`contrast` ilegible sobre sólidos** — el texto sobre el sólido de color
|
|
|
resolvía al paso 12 de la escala (casi invisible). Fix: el slot `contrast` por
|
|
|
defecto resuelve a `var(--color-content-on-solid, …)`. Por eso el primario
|
|
|
carbón lleva texto casi blanco sin configurar nada extra. (§20.2)
|
|
|
|
|
|
## Hallazgo de autoría: `bind:value` debe respetar el default `$bindable`
|
|
|
|
|
|
La página tuvo un síntoma engañoso: bajo SSR renderizaba completa (~1.1 MB) pero
|
|
|
la hidratación en cliente **desmontaba** casi todo el árbol (los componentes
|
|
|
quedaban como anclas vacías `<!---->`), sin error visible en consola. Parecía un
|
|
|
fallo de hidratación de los componentes con portal.
|
|
|
|
|
|
La causa real era un único `bind:value={menubarValue}` con
|
|
|
`menubarValue = $state(undefined)`, mientras `<Menubar>` declara
|
|
|
`value = $bindable('')`. Svelte 5 lanza `props_invalid_value` («Cannot do
|
|
|
`bind:value={undefined}` when `value` has a fallback value») **durante** la
|
|
|
hidratación; el error aborta el render de la página y desmonta el árbol.
|
|
|
|
|
|
Lección: al usar `bind:` contra un prop con default `$bindable`, el estado local
|
|
|
debe inicializarse con un valor del mismo tipo que el default — nunca `undefined`.
|
|
|
Aquí: `menubarValue = $state('')` y `align = $state<string[]>(['left'])`
|
|
|
(`ToggleGroup` usa `value = $bindable([])`, también para `type="single"`).
|
|
|
|
|
|
No era un bug del engine de theming ni de los portales: los componentes con portal
|
|
|
(Popover / Dialog / Menubar / DropdownMenu / Tooltip) renderizan, hidratan y abren
|
|
|
correctamente cuando el `bind:value` es válido.
|