|
|
|
|
# `eidos/components/`
|
|
|
|
|
|
|
|
|
|
Recipes + wrappers visuales por componente. Cada componente vive en su
|
|
|
|
|
**subdirectorio** con la forma canónica documentada abajo. Los `.css`
|
|
|
|
|
sueltos al nivel raíz son legacy de la fase "eidos = solo CSS" y se
|
|
|
|
|
migran progresivamente.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Forma canónica (disciplined option C — vigente desde 2026-05-10)
|
|
|
|
|
|
|
|
|
|
Convención unificada para TODOS los componentes multi-part. Sigue la
|
|
|
|
|
estructura de [air](../../air-old/) y la ergonomía moderna de bits-ui /
|
|
|
|
|
shadcn-svelte: una sola entidad mental — `<Drawer>` — con hijos
|
|
|
|
|
accesibles como propiedades — `<Drawer.Trigger>`, `<Drawer.Content>`, etc.
|
|
|
|
|
|
|
|
|
|
### Reglas duras
|
|
|
|
|
|
|
|
|
|
1. **Un solo punto de entrada por componente**: la default export es el
|
|
|
|
|
componente root visual. Se llama igual que el componente
|
|
|
|
|
(`<Drawer>`, `<Tabs>`, `<Checkbox>`, …) — **no** `<Drawer.Provider>`,
|
|
|
|
|
**no** `<Drawer.Root>`.
|
|
|
|
|
2. **El root vive en `{name}.svelte`** — NO en `{name}-provider.svelte`.
|
|
|
|
|
El nombre del fichero coincide con el nombre del componente.
|
|
|
|
|
3. **No exportar `Provider` públicamente**. La separación
|
|
|
|
|
"Provider compound vs flat" es una invención previa que se ha
|
|
|
|
|
retirado: hay UNA forma compound — el root + sus hijos atados.
|
|
|
|
|
4. **Los hijos siguen el naming de air / headless**: `Trigger`, `Content`,
|
|
|
|
|
`Overlay`, `Title`, `Description`, `Close`, `Portal`, `Header`,
|
|
|
|
|
`Footer`, `Item`, `Indicator`, `HiddenInput`, `Group`, `Label`, etc.
|
|
|
|
|
No inventar nombres nuevos.
|
|
|
|
|
5. **`Portal` se incluye donde air lo tenía** (Dialog, Drawer, Popover,
|
|
|
|
|
Tooltip — overlays portaled). Importado de
|
|
|
|
|
`$soma/components/internal`.
|
|
|
|
|
6. **No flat con snippet slots como API principal**. La invención
|
|
|
|
|
`<Drawer trigger={...} title={...} actions={...}>` está retirada —
|
|
|
|
|
esconde decisiones de composición que deberían ser explícitas en
|
|
|
|
|
componentes con portal/overlay/content/close.
|
|
|
|
|
7. **Los hijos se atan al root con asignación explícita**, no
|
|
|
|
|
`Object.assign` (que en Svelte 5 puede causar issues sutiles de
|
|
|
|
|
hidratación):
|
|
|
|
|
```ts
|
|
|
|
|
const Drawer = DrawerComponent as DrawerNamespace;
|
|
|
|
|
Drawer.Trigger = Trigger;
|
|
|
|
|
Drawer.Content = Content;
|
|
|
|
|
// ...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Estructura de directorio
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
drawer/
|
|
|
|
|
drawer.svelte ← root (lo que era air's Provider)
|
|
|
|
|
drawer-trigger.svelte
|
|
|
|
|
drawer-overlay.svelte
|
|
|
|
|
drawer-content.svelte
|
|
|
|
|
drawer-handle.svelte
|
|
|
|
|
drawer-title.svelte
|
|
|
|
|
drawer-description.svelte
|
|
|
|
|
drawer-close.svelte
|
|
|
|
|
drawer-header.svelte ← eidos-only layout shell (cuando aplique)
|
|
|
|
|
drawer-footer.svelte ← idem
|
|
|
|
|
drawer.css ← recipe
|
|
|
|
|
types.ts ← extiende las props públicas de Soma
|
|
|
|
|
index.ts ← compone Drawer + hijos
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `{name}.svelte` — el root
|
|
|
|
|
|
|
|
|
|
Wrapper directo sobre el provider público de Soma. Setea el
|
|
|
|
|
contexto headless, acepta los bindables del estado (`open`, `value`,
|
|
|
|
|
`pressed`, etc.), añade los data-attrs visuales propios de eidos
|
|
|
|
|
(`data-size`, `data-variant`, `data-color`, …), y renderiza
|
|
|
|
|
`{@render children?.()}` para que los hijos se compongan dentro.
|
|
|
|
|
|
|
|
|
|
```svelte
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
/**
|
|
|
|
|
* Eidos `<Drawer>` — root component. Wraps the Soma
|
|
|
|
|
* to set up the component context.
|
|
|
|
|
*/
|
|
|
|
|
import * as Drawer from '$soma/components/drawer';
|
|
|
|
|
import type { DrawerProps } from './types';
|
|
|
|
|
|
|
|
|
|
let {
|
|
|
|
|
open = $bindable(false),
|
|
|
|
|
activeSnapPoint = $bindable(null),
|
|
|
|
|
isDragging = $bindable(false),
|
|
|
|
|
children,
|
|
|
|
|
...rest
|
|
|
|
|
}: DrawerProps = $props();
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<Drawer.Provider {...rest} bind:open bind:activeSnapPoint bind:isDragging>
|
|
|
|
|
{@render children?.()}
|
|
|
|
|
</Drawer.Provider>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> **Patrón de import interno**: `import * as Drawer from '$soma/components/drawer'`.
|
|
|
|
|
> Eidos no crea una fachada headless propia por componente; envuelve las
|
|
|
|
|
> partes públicas de Soma directamente como `<Drawer.Provider>`,
|
|
|
|
|
> `<Drawer.Trigger>`, etc.
|
|
|
|
|
|
|
|
|
|
#### Prohibiciones de naming interno
|
|
|
|
|
|
|
|
|
|
El namespace interno que apunta a Soma se llama igual que el componente. No se
|
|
|
|
|
prefija ni se renombra:
|
|
|
|
|
|
|
|
|
|
```svelte
|
|
|
|
|
<!-- Correcto -->
|
|
|
|
|
import * as Collapsible from '$soma/components/collapsible';
|
|
|
|
|
|
|
|
|
|
<Collapsible.Provider>
|
|
|
|
|
<Collapsible.Trigger />
|
|
|
|
|
</Collapsible.Provider>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Formas prohibidas:
|
|
|
|
|
|
|
|
|
|
- `import * as Parts from '$soma/components/collapsible'`
|
|
|
|
|
- `import * as CollapsibleBase from '$soma/components/collapsible'`
|
|
|
|
|
- `import { Provider as SomaCollapsibleProvider } from '$soma/components/collapsible'`
|
|
|
|
|
- etiquetas sueltas `<Provider>`, `<Trigger>`, `<Content>` dentro de Eidos
|
|
|
|
|
- tipos o aliases internos `SomaXxxProvider`, `XxxBase`, `XxxRoot`
|
|
|
|
|
|
|
|
|
|
Motivo: Eidos ya declara la capa en el path. El import interno debe expresar la
|
|
|
|
|
entidad headless concreta de Soma, no una abstraccion inventada. Si un wrapper
|
|
|
|
|
Eidos necesita el provider de Soma, escribe `<Collapsible.Provider>`; si
|
|
|
|
|
necesita una parte, escribe `<Collapsible.Trigger>`, `<Collapsible.Content>`,
|
|
|
|
|
etc.
|
|
|
|
|
|
|
|
|
|
### `{name}-{part}.svelte` — hijos passthrough
|
|
|
|
|
|
|
|
|
|
Cada hijo es un wrapper delgado sobre la part del Soma. Si
|
|
|
|
|
añade visual-only data-attrs, los stamp aquí (ej. `data-size` en
|
|
|
|
|
Content). Si es passthrough puro (Trigger, Close), trivial.
|
|
|
|
|
|
|
|
|
|
```svelte
|
|
|
|
|
<!-- drawer-trigger.svelte -->
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import * as Drawer from '$soma/components/drawer';
|
|
|
|
|
import type { DrawerTriggerProps } from './types';
|
|
|
|
|
|
|
|
|
|
let { children, ...rest }: DrawerTriggerProps = $props();
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<Drawer.Trigger {...rest}>
|
|
|
|
|
{@render children?.()}
|
|
|
|
|
</Drawer.Trigger>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `index.ts` — compone el namespace
|
|
|
|
|
|
|
|
|
|
Asignación explícita per-property sobre el root component. **NO** usar
|
|
|
|
|
`Object.assign(DrawerComponent, { Trigger, ... })` — Svelte 5 puede manejar
|
|
|
|
|
mal la mutación bulk del component constructor durante hidratación,
|
|
|
|
|
causando que los hijos aparezcan y desaparezcan después de mount.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import DrawerComponent from './drawer.svelte';
|
|
|
|
|
import Trigger from './drawer-trigger.svelte';
|
|
|
|
|
import Overlay from './drawer-overlay.svelte';
|
|
|
|
|
import Content from './drawer-content.svelte';
|
|
|
|
|
import Title from './drawer-title.svelte';
|
|
|
|
|
import Description from './drawer-description.svelte';
|
|
|
|
|
import Close from './drawer-close.svelte';
|
|
|
|
|
import Header from './drawer-header.svelte';
|
|
|
|
|
import Footer from './drawer-footer.svelte';
|
|
|
|
|
import Handle from './drawer-handle.svelte';
|
|
|
|
|
import { Portal } from '$soma/components/internal';
|
|
|
|
|
|
|
|
|
|
type DrawerNamespace = typeof DrawerComponent & {
|
|
|
|
|
Trigger: typeof Trigger;
|
|
|
|
|
Portal: typeof Portal;
|
|
|
|
|
Overlay: typeof Overlay;
|
|
|
|
|
Content: typeof Content;
|
|
|
|
|
Handle: typeof Handle;
|
|
|
|
|
Title: typeof Title;
|
|
|
|
|
Description: typeof Description;
|
|
|
|
|
Close: typeof Close;
|
|
|
|
|
Header: typeof Header;
|
|
|
|
|
Footer: typeof Footer;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Drawer = DrawerComponent as DrawerNamespace;
|
|
|
|
|
Drawer.Trigger = Trigger;
|
|
|
|
|
Drawer.Portal = Portal;
|
|
|
|
|
Drawer.Overlay = Overlay;
|
|
|
|
|
Drawer.Content = Content;
|
|
|
|
|
Drawer.Handle = Handle;
|
|
|
|
|
Drawer.Title = Title;
|
|
|
|
|
Drawer.Description = Description;
|
|
|
|
|
Drawer.Close = Close;
|
|
|
|
|
Drawer.Header = Header;
|
|
|
|
|
Drawer.Footer = Footer;
|
|
|
|
|
|
|
|
|
|
export { Drawer };
|
|
|
|
|
export default Drawer;
|
|
|
|
|
|
|
|
|
|
export type {
|
|
|
|
|
DrawerProps,
|
|
|
|
|
DrawerTriggerProps as TriggerProps,
|
|
|
|
|
DrawerOverlayProps as OverlayProps,
|
|
|
|
|
DrawerContentProps as ContentProps
|
|
|
|
|
// … etc
|
|
|
|
|
} from './types';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### `types.ts` — passthrough + adiciones eidos
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import type {
|
|
|
|
|
ProviderProps,
|
|
|
|
|
TriggerProps,
|
|
|
|
|
ContentProps
|
|
|
|
|
// …
|
|
|
|
|
} from '$soma/components/drawer';
|
|
|
|
|
|
|
|
|
|
export type DrawerProps = ProviderProps;
|
|
|
|
|
export type DrawerTriggerProps = TriggerProps;
|
|
|
|
|
|
|
|
|
|
// Eidos añade props visuales que el recipe consume vía data-attrs
|
|
|
|
|
export type DrawerContentProps = ContentProps & {
|
|
|
|
|
size?: ResponsiveProp<DrawerSize>;
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> **Sin `XxxFlatProps`, sin `XxxProviderProps as ProviderProps` aliases.**
|
|
|
|
|
> Hay un solo `XxxProps` (el del root) y los `XxxPartProps` per hijo.
|
|
|
|
|
|
|
|
|
|
Cuando el tipo root de Soma se importa como `ProviderProps`, se usa solo como
|
|
|
|
|
tipo local dentro de `types.ts`. No se exporta al consumidor con nombre
|
|
|
|
|
`ProviderProps` desde Eidos ni se crea un alias `SomaXxxProviderProps`.
|
|
|
|
|
|
|
|
|
|
### Tipos visuales canonicos
|
|
|
|
|
|
|
|
|
|
No se redeclaran variantes, colores ni tamanos a mano en cada componente.
|
|
|
|
|
La unica fuente para las props visuales compartidas es
|
|
|
|
|
`src/uix/eidos/lib/types.ts`:
|
|
|
|
|
|
|
|
|
|
- `Size` y `ResponsiveProp<T>` para escalas responsivas.
|
|
|
|
|
- `ControlVariant`, `SelectionVariant`, `ChipVariant`, `MarkerVariant`,
|
|
|
|
|
`SurfaceVariant`, etc. para variantes canonicas por familia visual.
|
|
|
|
|
- `ColorRole` para la paleta de intents UIX (`primary`, `secondary`,
|
|
|
|
|
`neutral`, `affirm`, `fulfill`, `risk`, `threat`, `loss`).
|
|
|
|
|
|
|
|
|
|
Cuando un componente necesita restringir una familia compartida, usa
|
|
|
|
|
`Extract<>` sobre el tipo canonico. No se crean unions locales con literales
|
|
|
|
|
duplicados:
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import type { ColorRole, ControlVariant, Size } from '$uix/eidos/lib/types';
|
|
|
|
|
|
|
|
|
|
export type DateFieldSize = Extract<Size, 'sm' | 'md' | 'lg'>;
|
|
|
|
|
export type DateFieldVariant = ControlVariant;
|
|
|
|
|
export type DateFieldColor = ColorRole;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Partes Eidos-only
|
|
|
|
|
|
|
|
|
|
Algunas partes existen solo para componer la superficie visual: `Header`,
|
|
|
|
|
`Footer`, `Status`, `Main`, `Handle`, filas/labels planas, indicadores
|
|
|
|
|
decorativos o primitives `svg`. No necesitan morfo propio mientras cumplan
|
|
|
|
|
las cuatro reglas:
|
|
|
|
|
|
|
|
|
|
1. No crean comportamiento ni estado.
|
|
|
|
|
2. No son target de eventos perceptivos.
|
|
|
|
|
3. No poseen ARIA obligatoria ni relaciones accesibles propias.
|
|
|
|
|
4. Solo emiten estructura, clase/estilo passthrough o `data-*` visuales que
|
|
|
|
|
el recipe consume.
|
|
|
|
|
|
|
|
|
|
Si cualquiera de esas reglas deja de cumplirse, la parte deja de ser
|
|
|
|
|
Eidos-only y debe subir al contrato correspondiente: primero morfo, luego Soma
|
|
|
|
|
si necesita runtime.
|
|
|
|
|
|
|
|
|
|
El agregador `scripts/eidos-lint-all.ts` mantiene una allowlist explícita de
|
|
|
|
|
estos attrs/parts visuales. El lint base sigue siendo estricto con valores
|
|
|
|
|
inválidos de enums morfo; la allowlist solo evita que el reporte de drift se
|
|
|
|
|
llene de partes visuales intencionales.
|
|
|
|
|
|
|
|
|
|
`src/uix/eidos/component-api-contract.test.ts` protege la forma pública del
|
|
|
|
|
barrel: sin `Object.assign`, sin `Provider` público, root en
|
|
|
|
|
`{component}.svelte` y miembros del `XxxNamespace` sincronizados con sus
|
|
|
|
|
asignaciones explícitas (`Drawer.Trigger = Trigger`, etc.).
|
|
|
|
|
|
|
|
|
|
La guardia `src/uix/eidos/component-visual-attrs.test.ts` fija el cableado
|
|
|
|
|
mínimo entre wrapper y recipe: si un wrapper visual declara una prop que se
|
|
|
|
|
consume como `data-*` (`size`, `variant`, `color`, `position`, `columns`,
|
|
|
|
|
etc.), el fichero debe seguir estampando ese atributo. Esto no añade
|
|
|
|
|
comportamiento a Eidos; solo evita que la capa visual trague props en silencio.
|
|
|
|
|
|
|
|
|
|
### Uso del consumidor
|
|
|
|
|
|
|
|
|
|
```svelte
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import { Drawer } from '$uix/eidos/components/drawer';
|
|
|
|
|
let open = $state(false);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<Drawer bind:open variant="overlay" direction="right">
|
|
|
|
|
<Drawer.Trigger>Open</Drawer.Trigger>
|
|
|
|
|
<Drawer.Portal>
|
|
|
|
|
<Drawer.Overlay />
|
|
|
|
|
<Drawer.Content>
|
|
|
|
|
<Drawer.Header>
|
|
|
|
|
<Drawer.Title>Title</Drawer.Title>
|
|
|
|
|
<Drawer.Description>Subtitle</Drawer.Description>
|
|
|
|
|
</Drawer.Header>
|
|
|
|
|
<p>body</p>
|
|
|
|
|
<Drawer.Footer>
|
|
|
|
|
<Drawer.Close>Close</Drawer.Close>
|
|
|
|
|
</Drawer.Footer>
|
|
|
|
|
</Drawer.Content>
|
|
|
|
|
</Drawer.Portal>
|
|
|
|
|
</Drawer>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Componentes single-part (toggle, icon)
|
|
|
|
|
|
|
|
|
|
Sin hijos compound. El default export ES el componente entero. Sin
|
|
|
|
|
namespace, sin asignación de propiedades, sin `Provider` alias.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
// toggle/index.ts
|
|
|
|
|
export { default } from './toggle.svelte';
|
|
|
|
|
export type { ToggleProps, ToggleVariant, ToggleSize } from './types';
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```svelte
|
|
|
|
|
import Toggle from '$uix/eidos/components/toggle';
|
|
|
|
|
<Toggle bind:pressed variant="solid">Bold</Toggle>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Partes visuales por defecto
|
|
|
|
|
|
|
|
|
|
Algunos componentes multi-part necesitan una parte visual minima para no
|
|
|
|
|
renderizar una superficie rota. `Switch` es el caso canonico: el root sigue
|
|
|
|
|
exponiendo `Switch.Thumb`, pero si el consumidor no aporta children, `<Switch />`
|
|
|
|
|
monta un thumb visual por defecto. Esto no crea una API flat ni esconde el
|
|
|
|
|
contrato de Soma; solo evita que el uso minimo renderice un track sin indicador.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Toast — caso especial (dos roots independientes)
|
|
|
|
|
|
|
|
|
|
`Toast` tiene dos roots públicos que NO se anidan:
|
|
|
|
|
|
|
|
|
|
- `<Toast>` — manual compound (Provider+Viewport+Item iteración del
|
|
|
|
|
consumer). Children attached: Viewport, Item, Status, Main, Title,
|
|
|
|
|
Description, Action, Close.
|
|
|
|
|
- `<Toaster />` — imperative auto-mount. Renderiza Provider+Viewport+
|
|
|
|
|
Item-loop con un template default. Pasas `toaster` de
|
|
|
|
|
`createToaster()`. Export separado, no `Toast.Toaster` porque es un
|
|
|
|
|
root competidor, no un hijo.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { Toast, Toaster, createToaster } from '$uix/eidos/components/toast';
|
|
|
|
|
|
|
|
|
|
const t = createToaster();
|
|
|
|
|
|
|
|
|
|
// Imperative
|
|
|
|
|
<Toaster toaster={t} />
|
|
|
|
|
|
|
|
|
|
// Manual
|
|
|
|
|
<Toast toaster={t}>
|
|
|
|
|
<Toast.Viewport>
|
|
|
|
|
{#each t.toasts as toast}
|
|
|
|
|
<Toast.Item {toast}>
|
|
|
|
|
<Toast.Title>{toast.title}</Toast.Title>
|
|
|
|
|
</Toast.Item>
|
|
|
|
|
{/each}
|
|
|
|
|
</Toast.Viewport>
|
|
|
|
|
</Toast>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Convenciones de naming de tokens CSS
|
|
|
|
|
|
|
|
|
|
Los tokens CSS llevan el nombre del **componente**, no de la **capa**.
|
|
|
|
|
Sin prefijos de capa — ni `--eidos-`, ni `--air-`, ni `--terra-`,
|
|
|
|
|
ni `--soma-`.
|
|
|
|
|
|
|
|
|
|
| Forma | Uso | Ejemplo |
|
|
|
|
|
| ------------------ | ----------------------------------------------------- | ------------------------------------------- |
|
|
|
|
|
| `--{component}-…` | tokens públicos (sobreescribibles por el consumer) | `--dialog-content-bg`, `--toggle-radius-md` |
|
|
|
|
|
| `--_{component}-…` | tokens internos del recipe (no parte del API público) | `--_tabs-trigger-height`, `--_toggle-bg` |
|
|
|
|
|
|
|
|
|
|
Los `[data-{component}]` y `[data-{component}-{part}]` selectors son la
|
|
|
|
|
única vía pública para que el recipe se acople al runtime — toda
|
|
|
|
|
información cross-layer pasa por data-attrs declarados en el morfo.
|
|
|
|
|
Cada `--{component}-*` consumido por CSS debe salir de `EidosConfig.recipes`;
|
|
|
|
|
si un alias publico queda sin consumidor real, el test
|
|
|
|
|
`src/uix/eidos/recipe-css-contract.test.ts` falla.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Comparativa obligatoria por componente
|
|
|
|
|
|
|
|
|
|
Ningun componente Eidos se declara cerrado solo por envolver Soma. Antes de
|
|
|
|
|
implementar o revisar un componente:
|
|
|
|
|
|
|
|
|
|
1. Leer Air en la rama anterior (`morfo-runtime:src/uix/air/components/{name}`)
|
|
|
|
|
cuando exista. Air es la primera baseline visual.
|
|
|
|
|
2. Leer Soma/Morfo actuales para separar comportamiento, ARIA, estado,
|
|
|
|
|
traducciones y data-attrs de la superficie visual Eidos.
|
|
|
|
|
3. Auditar Morfo/Sema. El morfo no se considera correcto solo porque compile:
|
|
|
|
|
- Clasificar el componente como pasivo, interactivo o mixto.
|
|
|
|
|
- Justificar cualquier `0 events` de forma explicita.
|
|
|
|
|
- Para cada accion real de usuario revisar `family`, `verb`, `sequence`,
|
|
|
|
|
`intent`, `target`, `prewrite` y `commit`.
|
|
|
|
|
- Verificar que Soma dispara esas ocurrencias con `runtime.trigger(...)`.
|
|
|
|
|
- Evitar eventos de alta frecuencia para cambios continuos; normalmente se
|
|
|
|
|
modelan inicio/drag confirmado/commit, no cada frame.
|
|
|
|
|
4. Comparar contra referentes externos relevantes: Radix/Radix Themes, Ark UI,
|
|
|
|
|
Bits UI, shadcn-svelte y React Aria cuando aplique.
|
|
|
|
|
5. Crear/actualizar `components/{name}/README.md` con tabla de funcionalidades,
|
|
|
|
|
tabla Morfo/Sema, gaps y decisiones. Cada `⚠️` / `❌` debe acabar en una
|
|
|
|
|
decision explicita: implementar ahora, diferir a Morfo/Soma, diferir a v2 o
|
|
|
|
|
descartar por no pertenecer a Eidos.
|
|
|
|
|
6. Si hay demo en `web/routes/uix/components/{name}`, sus snippets forman parte
|
|
|
|
|
de la arquitectura del componente: deben reproducir el mismo contrato visible
|
|
|
|
|
que el preview. En componentes schema-driven (`form`, `auto-fields`,
|
|
|
|
|
date/time con formatos, etc.) no se permite un schema reducido que omita
|
|
|
|
|
campos visibles, validators o defaults reales.
|
|
|
|
|
7. Solo despues tocar wrapper, recipe o tokens.
|
|
|
|
|
|
|
|
|
|
El objetivo no es copiar APIs, sino que Eidos no quede por debajo de Air ni de
|
|
|
|
|
los referentes en funcionalidades reales. Si una capacidad pertenece a Soma, la
|
|
|
|
|
tabla debe decirlo; si es visual, Eidos debe cubrirla o justificar el gap.
|
|
|
|
|
|
|
|
|
|
### Reglas especificas para demos de formularios
|
|
|
|
|
|
|
|
|
|
- La validacion en tiempo real se modela como
|
|
|
|
|
`validationBehaviour: 'onChange'`.
|
|
|
|
|
- Si una demo arranca en `onChange`, los defaults iniciales deben ser validos
|
|
|
|
|
salvo que el README y la UI digan explicitamente que se esta mostrando un
|
|
|
|
|
formulario inicialmente invalido.
|
|
|
|
|
- El snippet de Soma y el snippet de Eidos deben declarar los mismos campos
|
|
|
|
|
visibles que el preview: imports SIUM, schema, defaults, `Form + Field` y
|
|
|
|
|
`Form.AutoFields`. No se admiten snippets que omiten `age`, `role`,
|
|
|
|
|
campos de fecha, arrays u otros datos que el preview valida.
|
|
|
|
|
- `Form.AutoFields` es renderer reflectivo de SIUM. Si necesita comportamiento
|
|
|
|
|
nuevo, se cambia Soma / `$libs/forms` / Morfo antes que Eidos.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Forma legacy (CSS-only, retired)
|
|
|
|
|
|
|
|
|
|
Hubo una fase anterior cuando eidos sólo emitía CSS (`accordion.css`,
|
|
|
|
|
`dialog.css`, etc., al nivel raíz de `components/`). Todos los
|
|
|
|
|
componentes han sido migrados a la forma canónica del subdirectorio.
|
|
|
|
|
Si encuentras un `.css` suelto, es un descuido — debe vivir dentro de
|
|
|
|
|
su subdirectorio.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Estado actual de la migración (2026-05-20)
|
|
|
|
|
|
|
|
|
|
La tanda 2026-05-17 reanuda la migración Soma -> Eidos por orden explícita.
|
|
|
|
|
Los wrappers nuevos siguen el mismo criterio: envolver partes públicas de Soma,
|
|
|
|
|
añadir sólo props visuales (`size` en esta tanda) y dejar comportamiento,
|
|
|
|
|
estado, ARIA, traducciones y escritura headless en Soma/Morfo.
|
|
|
|
|
|
|
|
|
|
| Componente | Forma canónica | Notas |
|
|
|
|
|
| ------------ | -------------- | ------------------------------------------------------------------------------- |
|
|
|
|
|
| toggle | ✅ single-part | piloto |
|
|
|
|
|
| switch | ✅ multi-part | Thumb |
|
|
|
|
|
| collapsible | ✅ multi-part | Trigger, Content |
|
|
|
|
|
| dialog | ✅ multi-part | Trigger, Portal, Overlay, Content, Title, Description, Close, Header, Footer |
|
|
|
|
|
| drawer | ✅ multi-part | + Handle |
|
|
|
|
|
| field | ✅ multi-part | Label, RequiredIndicator, Control, Input, HelperText, ErrorText, Prefix, Suffix |
|
|
|
|
|
| form | ✅ multi-part | Submit, Reset, ErrorSummary, AutoFields |
|
|
|
|
|
| popover | ✅ multi-part | Arrow, Anchor, Title, Description |
|
|
|
|
|
| toast | ✅ multi-part | + Toaster separate |
|
|
|
|
|
| accordion | ✅ multi-part | Item, Header, Trigger, Content |
|
|
|
|
|
| avatar | ✅ multi-part | Image, Fallback (eidos-native) |
|
|
|
|
|
| breadcrumb | ✅ multi-part | List, Item, Link, Separator, Ellipsis |
|
|
|
|
|
| calendar | ✅ multi-part | Header, Heading, Prev/Next, Month/YearSelect, Grid, Cell, Day |
|
|
|
|
|
| icon | ✅ single-part | + 1697 lucide glyphs |
|
|
|
|
|
| meter | ✅ multi-part | Indicator |
|
|
|
|
|
| number-field | ✅ multi-part | Input, IncrementTrigger, DecrementTrigger, Scrubber |
|
|
|
|
|
| pagination | ✅ multi-part | FirstTrigger, PrevTrigger, NextTrigger, LastTrigger, Item, Ellipsis |
|
|
|
|
|
| progress | ✅ multi-part | Label, ValueText, Indicator |
|
|
|
|
|
| rating-group | ✅ multi-part | Item |
|
|
|
|
|
| search-field | ✅ multi-part | Input, ClearTrigger |
|
|
|
|
|
| select | ✅ multi-part | Trigger, Value, Indicator, Portal, Content, Viewport, Item, ItemIndicator, Group |
|
|
|
|
|
| combobox | ✅ multi-part | Control, Input, Trigger, Indicator, Portal, Content, Viewport, Item, ItemIndicator |
|
|
|
|
|
| slider | ✅ multi-part | Range, Thumb, Tick |
|
|
|
|
|
| tooltip | ✅ multi-part | + Group |
|
|
|
|
|
| tabs | ✅ multi-part | List, Trigger, Content, Indicator |
|
|
|
|
|
| checkbox | ✅ multi-part | Indicator, HiddenInput, Group, GroupLabel |
|
|
|
|
|
| radio-group | ✅ multi-part | Item, Indicator, HiddenInput, Label |
|
|
|
|
|
| toolbar | ✅ multi-part | Button, Link, Group, GroupItem, Separator |
|
|
|
|
|
| tag-group | ✅ multi-part | Label, Item, Link, RemoveButton |
|
|
|
|
|
| tags-input | ✅ multi-part | Control, Input, Item, ItemText, ItemDeleteTrigger, ClearTrigger |
|
|
|
|
|
| file-upload | ✅ multi-part | Label, Dropzone, Trigger, HiddenInput, FileList, Item, preview/progress/actions |
|
|
|
|
|
| editable | ✅ multi-part | Area, Control, Preview, Input, EditTrigger, SubmitTrigger, CancelTrigger |
|
|
|
|
|
| stepper | ✅ multi-part | List, Item, Trigger, Indicator, Separator, Content, CompletedContent, Prev/Next |
|
|
|
|
|
|
|
|
|
|
### Orden recomendado para continuar
|
|
|
|
|
|
|
|
|
|
1. Componentes grandes sólo con tabla previa de migración:
|
|
|
|
|
`date-picker`, `date-range-picker`, `time-field`, `time-picker`.
|