diff --git a/continue.md b/continue.md index 18b56fce0..0ef528474 100644 --- a/continue.md +++ b/continue.md @@ -13,6 +13,23 @@ Fecha de corte: 2026-05-17. Rama: `active-uix`. - `npm run check` -> 0 errores, 0 warnings. - `node --import tsx/esm scripts/eidos-lint-all.ts` -> 0 invalid, sin drift hotspots. +- Regla nueva para continuar Eidos componente por componente: + - La primera referencia es Air en la rama anterior + (`glm-5:src/uix/air/components/{name}`) cuando exista. + - Despues se compara con Soma/Morfo actuales y con referentes externos + relevantes (Radix/Radix Themes, Ark UI, Bits UI, shadcn-svelte y React Aria + si aplica). + - Cada componente Eidos debe tener tabla en `components/{name}/README.md` + con gaps y decisiones antes de tocar wrapper/recipe/tokens. + - Objetivo: no dejar Eidos por debajo de Air ni de las plataformas de + referencia en funcionalidades reales. +- Primer componente auditado con el nuevo protocolo: + - `Dialog`: comparado contra Air, Radix, Ark UI, Bits UI y shadcn-svelte. + - Decision: no se crea `Dialog.Positioner`; `Content.position` cubre esa + responsabilidad visual sobre la grid canonica 3x3. + - Decision: se anaden overrides visuales `width`, `minWidth`, `maxWidth`, + `height`, `minHeight`, `maxHeight` en `Dialog.Content`, serializados como + variables privadas del recipe. Soma sigue siendo dueno de comportamiento. - Componentes Eidos nuevos desde Soma: - `meter` - `progress` diff --git a/src/uix/eidos/components/README.md b/src/uix/eidos/components/README.md index 81b097f44..f7896a6f1 100644 --- a/src/uix/eidos/components/README.md +++ b/src/uix/eidos/components/README.md @@ -330,6 +330,28 @@ si un alias publico queda sin consumidor real, el test --- +## 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 (`glm-5: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. Comparar contra referentes externos relevantes: Radix/Radix Themes, Ark UI, + Bits UI, shadcn-svelte y React Aria cuando aplique. +4. Crear/actualizar `components/{name}/README.md` con tabla de funcionalidades, + gaps y decisiones. Cada `⚠️` / `❌` debe acabar en una decision explicita: + implementar ahora, diferir a v2 o descartar por no pertenecer a Eidos. +5. 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. + +--- + ## Forma legacy (CSS-only, retired) Hubo una fase anterior cuando eidos sólo emitía CSS (`accordion.css`, diff --git a/src/uix/eidos/components/dialog/README.md b/src/uix/eidos/components/dialog/README.md new file mode 100644 index 000000000..1462e7e52 --- /dev/null +++ b/src/uix/eidos/components/dialog/README.md @@ -0,0 +1,68 @@ +# Dialog + +Visual dialog wrapper over Soma's dialog primitive. + +## Baseline + +Air exposed `Root`, `Trigger`, `Portal`, `Overlay`, `Content`, `Header`, +`Footer`, `Title`, `Description` and `Close`. The only visual prop on +`Content` was responsive `size: 'sm' | 'md' | 'lg' | 'full'`; contextual +motion lived in Air's motion helpers. Eidos keeps the same anatomy under the +disciplined option C shape (`` + attached parts), adds `xl`, panel +positioning and explicit dimension overrides, while Soma owns behavior. + +## Comparison + +| Feature | Air | Radix | Ark UI | Bits UI | shadcn-svelte | Eidos | +| --- | :--: | :--: | :--: | :--: | :--: | :--: | +| Compound API | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Portal | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Overlay / backdrop | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Header / footer visual shells | ✅ | example | example | custom | ✅ | ✅ | +| Responsive visual size | ✅ | ✅ | — | — | class | ✅ | +| Explicit width / height overrides | ❌ | ✅ | — | — | class | ✅ | +| Panel positioning | ❌ | custom CSS | ✅ `Positioner` | custom CSS | class | ✅ | +| Modal / non-modal | ✅ | ✅ | ✅ | ✅ | primitive | ✅ Soma | +| Alert dialog role | ✅ | separate primitive | ✅ | props | separate wrapper | ✅ Soma | +| Focus trap / restore | ✅ | ✅ | ✅ | ✅ | primitive | ✅ Soma | +| Escape / outside policies | ✅ | callbacks | ✅ | ✅ | primitive | ✅ Soma | +| Nested dialogs | ✅ | basic | ✅ | ✅ | primitive | ✅ Soma | +| Force mount for animation | ✅ | ✅ | ✅ | ✅ | primitive | ✅ Soma | +| Scroll lock | ✅ | ✅ | ✅ | ✅ | primitive | ✅ Soma | + +## Decisions + +- **No `Dialog.Positioner` part**. Ark separates `Positioner`; Eidos exposes + `Content.position` over the canonical 3x3 `Position` grid instead. A new part + would duplicate a visual responsibility already represented by `Content`. +- **Dimension overrides belong to Eidos**. `width`, `minWidth`, `maxWidth`, + `height`, `minHeight` and `maxHeight` are visual-only props on + `Dialog.Content`, serialized as private CSS variables. Soma remains unaware + of them. +- **Behavior stays in Soma**. Modal/non-modal mode, alertdialog role, focus, + dismissal, nesting, scroll lock and force mount are inherited from Soma. + +## Eidos API + +```svelte + + Open + + + + + Title + Description + + + Close + + + + +``` diff --git a/src/uix/eidos/components/dialog/dialog-content.svelte b/src/uix/eidos/components/dialog/dialog-content.svelte index 2ba571192..448ce46bf 100644 --- a/src/uix/eidos/components/dialog/dialog-content.svelte +++ b/src/uix/eidos/components/dialog/dialog-content.svelte @@ -15,6 +15,7 @@ * from the morfo runtime. */ import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; import * as Dialog from '$soma/components/dialog'; import type { DialogContentProps } from './types'; @@ -25,6 +26,13 @@ let { size = 'md', position = 'middle-center', + width, + minWidth, + maxWidth, + height, + minHeight, + maxHeight, + style, children: bodyContent, ...rest }: DialogContentProps = $props(); @@ -34,10 +42,41 @@ const resolvedSize = $derived(eidos.resolve(size, 'md')); const resolvedPosition = $derived(eidos.resolve(position, 'middle-center')); const isSheet = $derived(resolvedSize !== 'full' && eidos.isBelow('sm')); + + function serializeLength(value: string | number | undefined): string | undefined { + if (value === undefined) return undefined; + if (typeof value === 'number') { + if (!Number.isFinite(value)) return undefined; + return value === 0 ? '0' : `${value}px`; + } + const trimmed = value.trim(); + return trimmed || undefined; + } + + function dimensionVar(name: string, value: string | number | undefined): string | undefined { + const serialized = serializeLength(value); + return serialized ? `${name}: ${serialized};` : undefined; + } + + const dimensionsStyle = $derived( + [ + dimensionVar('--_dialog-content-width-override', serializeLength(eidos.resolve(width))), + dimensionVar('--_dialog-content-min-width-override', serializeLength(eidos.resolve(minWidth))), + dimensionVar('--_dialog-content-max-width-override', serializeLength(eidos.resolve(maxWidth))), + dimensionVar('--_dialog-content-height-override', serializeLength(eidos.resolve(height))), + dimensionVar('--_dialog-content-min-height-override', serializeLength(eidos.resolve(minHeight))), + dimensionVar('--_dialog-content-max-height-override', serializeLength(eidos.resolve(maxHeight))) + ] + .filter(Boolean) + .join(' ') + ); + + const composedStyle = $derived(composeInlineStyle(style, dimensionsStyle)); ; * blocking modal. Default `'middle-center'`. */ export type DialogPosition = Position; +export type DialogCssLength = string | number; export type DialogProps = SomaDialogProviderProps; export type DialogTriggerProps = SomaDialogTriggerProps; @@ -55,6 +56,23 @@ export type DialogContentProps = SomaDialogContentProps & { * @default 'middle-center' */ position?: ResponsiveProp; + /** + * Explicit content width override. Number values are serialized as px; + * strings accept any CSS length/expression (`'42rem'`, `'min(90vw, 720px)'`). + * This mirrors the Radix Themes dimension escape hatch without moving + * layout ownership to Soma. + */ + width?: ResponsiveProp; + /** Minimum content width override. Number values are serialized as px. */ + minWidth?: ResponsiveProp; + /** Maximum content width override. Number values are serialized as px. */ + maxWidth?: ResponsiveProp; + /** Explicit content height override. Number values are serialized as px. */ + height?: ResponsiveProp; + /** Minimum content height override. Number values are serialized as px. */ + minHeight?: ResponsiveProp; + /** Maximum content height override. Number values are serialized as px. */ + maxHeight?: ResponsiveProp; }; export type DialogOverlayProps = SomaDialogOverlayProps & {