5.5 KiB
Icon
Estado
Icon es un componente leaf visual de Eidos. No tiene contraparte Soma ni
Morfo porque no gestiona estado, ARIA relacional, teclado, foco ni eventos
perceptivos. Su contrato es SVG + tokens visuales.
Superficie publica:
Icondefault para SVGs custom.- 1696 glyphs Lucide exportados como componentes Svelte.
IntentIconcomo mapa canonicoIntent -> glyph.resolveIconSize()yresolveIconStrokeWidth()para tests, tooling y generacion.
Baseline
Air ya tenia un modulo icon completo:
| Capacidad | Air | Eidos |
|---|---|---|
| SVG wrapper custom | Si, Root |
Si, default Icon |
| Glyphs Lucide locales | Si | Si |
Size canonico xxs..xxl |
Si | Si |
Stroke auto/sm/md/lg |
Si | Si |
title + decorative |
Si | Si |
| Clase base | .air-icon scoped |
[data-icon] recipe |
| Tokens | --air-icon-* |
--icon-* |
| Intent glyphs | No | Si, IntentIcon |
absoluteStrokeWidth |
No | Si |
Decision: Eidos conserva la ergonomia de Air, elimina el prefijo de capa y
centraliza la visual en tokens/recipes. IntentIcon es una extension propia
del ecosistema porque los intents son canonicos en UIX.
Comparativa
| Plataforma | Modelo | Eidos |
|---|---|---|
| Lucide Svelte | Iconos standalone, props de size/color/stroke, tree-shaking, TS, accessibility y global styling. | Mismo modelo de componentes standalone, con tokens Eidos y absoluteStrokeWidth. |
| Radix Icons | Set React de iconos 15x15, imports individuales. | Mas flexible en size/stroke y disponible como componentes Svelte locales. |
| shadcn-svelte | No define una libreria de iconos propia; usa Lucide en los componentes y exige aria-label para botones icon-only. |
Mismo criterio: el icono es visual; la accesibilidad del control icon-only vive en el componente que lo contiene. |
| unplugin-icons/Iconify | Iconos on-demand de muchos packs via plugin de build. | No es objetivo de Eidos competir en catalogo dinamico multi-pack; Eidos prioriza glyphs locales, tipados y themeables. |
Fuentes:
Decisiones
Size y stroke
Los tamanos canonicos (xxs, xs, sm, md, lg, xl, xxl) se
resuelven a tokens --icon-size-*. El default visual usa --icon-size y
--icon-stroke-width, de modo que el theme puede cambiar el icono base sin
tocar cada consumidor.
absoluteStrokeWidth sigue el patron de Lucide: cuando el size es numerico,
px o canonico, ajusta el stroke-width para mantener grosor visual estable al
escalar el SVG.
Color
El wrapper usa stroke="currentColor" y color: inherit. El color pertenece al
contexto visual que contiene el icono, no al glyph. Si un consumidor necesita
color puntual, puede pasarlo como atributo SVG/CSS normal.
Accesibilidad
El default es decorative=true, por tanto se renderiza aria-hidden="true".
Cuando decorative=false, se renderiza role="img" y se puede aportar
title o atributos ARIA via passthrough. En botones icon-only, el label
accesible pertenece al boton, no al icono.
IntentIcon
IntentIcon no sustituye a los glyphs. Es una ayuda doctrinal para superficies
que deben expresar un Intent canonico (neutral, affirm, fulfill,
risk, threat, loss) sin duplicar tablas en Toast, Field, Banner, Dialog,
etc. La fuente unica vive en intent.ts.
Passive justification
Icon declara events: 0 deliberadamente. Es una primitiva visual leaf:
no gestiona estado interactivo, no responde a teclado, no participa en
flujos de focus, no emite señales perceptivas.
- El accesible name del control que contiene el icono (botón, link) pertenece a ese control, no al icono — patrón shadcn-svelte/Radix.
- La selección de glyph es estática (import al componente que lo usa); no hay un evento "icon changed" porque el cambio es del consumer.
- El feedback perceptivo de "el icono cambió" se emite desde el componente que lo orquesta (Toast cambia de glyph al cambiar de intent → Toast emite el sema event, no Icon).
Si una superficie necesita semántica al cambiar de icono, ese evento vive en el componente padre (Toast, Field, Banner, Dialog), no en Icon. Icon se mantiene pasivo para no duplicar responsabilidades.
Gaps
| Gap | Disposición | Detalle |
|---|---|---|
absoluteStrokeWidth de Lucide |
implementar | ✓ Ya implementado en Eidos. |
FolderProvider generado erroneamente |
implementar | ✓ Corregido a FolderRoot. |
Dynamic icon by string (<Icon name="check" />) |
diferir | Sólo se justifica si hay consumer real. Resoluble con mapa explícito o tooling sin convertir Icon en service locator. |
Global icon context tipo Lucide (<IconContext>) |
descartar | Los tokens Eidos (--icon-size, --icon-stroke-width) cubren ese caso de forma más coherente con el theme runtime. |
| Catálogo multi-pack tipo Iconify | descartar | Fuera de alcance: no pertenece al design system base. Catálogo dinámico multi-pack pertenece a tooling de build aparte. |
<Icon name="check" /> con autocompletado tipado |
diferir | Tipo union de los 1696 nombres es factible (IconName) pero infla .d.ts. Sin caso de uso real. |
| Iconos animados (spin/pulse) propios | diferir | Estado vive en CSS del consumidor (botón con data-loading). Mientras un caso no pida glyph animado autocontenido, no se construye. |