Add Eidos drawer reference comparison

active-uix
dev 5 months ago
parent 0b399cc6e8
commit 6707034fbd

@ -30,6 +30,17 @@ Fecha de corte: 2026-05-17. Rama: `active-uix`.
- 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.
- Segundo componente auditado con el nuevo protocolo:
- `Drawer`: comparado contra Air (`glm-5`), Soma/Morfo actuales, Vaul,
shadcn-svelte y Ark UI Dialog como referencia de dialog/layer.
- Decision: no se crea `Drawer.Positioner`; la geometria edge-anchored vive
en `data-side` + `data-variant` y en la receta.
- Decision: no se implementa `shouldScaleBackground` de Vaul en Drawer local;
es un efecto global sobre el wrapper de app y requiere contrato de shell.
- Decision: se anaden overrides visuales `width`, `minWidth`, `maxWidth`,
`height`, `minHeight`, `maxHeight` en `Drawer.Content`, serializados como
variables privadas del recipe. El size canonico `sm/md/lg/full` sigue como
preset principal.
- Componentes Eidos nuevos desde Soma:
- `meter`
- `progress`

@ -0,0 +1,48 @@
# Eidos Drawer
Fecha de revision: 2026-05-17.
## Baseline
Drawer parte de Air y se apoya en Soma para comportamiento. Eidos solo anade
superficie visual estable: `size`, medidas/lmites del panel y receta CSS.
## Comparativa
| Referencia | Superficie relevante | Estado Eidos |
| --- | --- | --- |
| Air `drawer` (`glm-5`) | `Root`, `Trigger`, `Portal`, `Overlay`, `Content`, `Header`, `Footer`, `Title`, `Description`, `Close`; `Content.size = sm/md/lg/full`; cuatro lados fisicos via Terra. | Cubierto. Se conserva la API compound y el `size` canonico. |
| Soma actual | `variant = overlay/inline/persistent`, `direction = top/bottom/left/right/start/end`, snap points, drag, `Handle`, dismissal, focus/scroll callbacks, `activeSnapPoint`, `minSize/maxSize`. | Cubierto por passthrough. Eidos no duplica estado ni gesture logic. |
| Vaul | `Root`, `Trigger`, `Portal`, `Overlay`, `Content`, `Handle`, `Title`, `Description`, `Close`; `modal`, `container`, `direction`, `dismissible`, `handleOnly`, `snapPoints`, `activeSnapPoint`, `fadeFromIndex`, `snapToSequentialPoint`. | Cubierto en lo esencial por Soma. `fadeFromIndex` queda como decision visual futura si el overlay necesita fade por indice. |
| shadcn-svelte | Wrapper sobre Vaul Svelte con `Root`, `Trigger`, `Content`, `Header`, `Footer`, `Title`, `Description`, `Close`; ejemplo de responsive dialog usando Dialog en desktop y Drawer en mobile. | Cubierto. Eidos mantiene `Header/Footer` como shells visuales y no introduce API flat. |
| Ark UI / Radix / Bits UI | No hay Drawer canonico con gestos equivalente; la referencia comparable es Dialog: portal, backdrop/overlay, content, title/description, close, focus trap, non-modal/lazy mount. | Cubierto desde Soma; Eidos no anade `Positioner` porque la geometria edge-anchored vive en la receta y en `direction`. |
Fuentes externas consultadas:
- Vaul API: https://vaul.emilkowal.ski/api
- Vaul snap points: https://vaul.emilkowal.ski/snap-points
- shadcn-svelte Drawer: https://tw3.shadcn-svelte.com/docs/components/drawer
- Ark UI Dialog: https://ark-ui.com/docs/components/dialog
## Decisiones
- No se reintroduce `Provider` publico ni API flat con snippets.
- No se crea `Drawer.Positioner`. Drawer no es Dialog centrado; su posicion
principal es edge-anchored y se deriva de `data-side` + `data-variant`.
- No se implementa `shouldScaleBackground` ahora. Es un efecto global sobre el
wrapper de la aplicacion y requiere un contrato explicito de ownership DOM,
no un estilo local de Drawer.
- `Drawer.Content` acepta `width`, `minWidth`, `maxWidth`, `height`,
`minHeight` y `maxHeight` como overrides visuales responsivos. Los valores se
serializan en variables privadas del recipe (`--_drawer-content-*`) para que
no entren en el contrato publico de tokens.
- `size` sigue siendo canonico y suficiente para la mayoria de usos; los
overrides son para layouts concretos: rail persistente, sheet de altura fija,
side panel ancho o constraints responsive.
## Pendiente deliberado
- Evaluar `fadeFromIndex` solo si el overlay de snap points necesita una
curva distinta por indice. Hoy Soma ya emite progreso y Eidos consume
opacidad de overlay.
- Evaluar background scaling como feature de capa/shell, no como recipe local.

@ -10,19 +10,61 @@
* runtime.
*/
import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style';
import * as Drawer from '$soma/components/drawer';
import type { DrawerContentProps } from './types';
// `children` is renamed to `bodyContent` so the inner `{#snippet children}`
// block can accept and forward Soma's `{ open, side, progress, isDragging }`
// snippet args without shadowing the prop.
let { size = 'md', children: bodyContent, ...rest }: DrawerContentProps = $props();
let {
size = 'md',
width,
minWidth,
maxWidth,
height,
minHeight,
maxHeight,
style,
children: bodyContent,
...rest
}: DrawerContentProps = $props();
const eidos = ActiveEidos.require();
const resolvedSize = $derived(eidos.resolve(size, 'md'));
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('--_drawer-content-width-override', serializeLength(eidos.resolve(width))),
dimensionVar('--_drawer-content-min-width-override', serializeLength(eidos.resolve(minWidth))),
dimensionVar('--_drawer-content-max-width-override', serializeLength(eidos.resolve(maxWidth))),
dimensionVar('--_drawer-content-height-override', serializeLength(eidos.resolve(height))),
dimensionVar('--_drawer-content-min-height-override', serializeLength(eidos.resolve(minHeight))),
dimensionVar('--_drawer-content-max-height-override', serializeLength(eidos.resolve(maxHeight)))
]
.filter(Boolean)
.join(' ')
);
const composedStyle = $derived(composeInlineStyle(style, dimensionsStyle));
</script>
<Drawer.Content {...rest} data-size={resolvedSize}>
<Drawer.Content {...rest} style={composedStyle} data-size={resolvedSize}>
{#snippet children(snippetProps)}
{@render bodyContent?.(snippetProps)}
{/snippet}

@ -75,8 +75,10 @@
/* ── Content (variant-shared base) ──────────────────────────────────────── */
[data-drawer-content] {
--_drawer-panel-width: var(--drawer-content-width-md);
--_drawer-panel-height: var(--drawer-content-height-md);
--_drawer-size-width: var(--drawer-content-width-md);
--_drawer-size-height: var(--drawer-content-height-md);
--_drawer-panel-width: var(--_drawer-content-width-override, var(--_drawer-size-width));
--_drawer-panel-height: var(--_drawer-content-height-override, var(--_drawer-size-height));
--_drawer-padding: var(--drawer-content-padding-md);
display: flex;
@ -84,6 +86,10 @@
gap: var(--drawer-stack-gap);
padding: var(--_drawer-padding);
overflow: auto;
min-inline-size: var(--_drawer-content-min-width-override, auto);
max-inline-size: var(--_drawer-content-max-width-override, none);
min-block-size: var(--_drawer-content-min-height-override, auto);
max-block-size: var(--_drawer-content-max-height-override, none);
border: var(--drawer-content-border-width) solid var(--drawer-content-border);
border-radius: var(--drawer-content-radius);
background: var(--drawer-content-bg);
@ -123,20 +129,20 @@
/* ── Content sizing (variant-shared) ────────────────────────────────────── */
[data-drawer-content][data-size='sm'] {
--_drawer-panel-width: var(--drawer-content-width-sm);
--_drawer-panel-height: var(--drawer-content-height-sm);
--_drawer-size-width: var(--drawer-content-width-sm);
--_drawer-size-height: var(--drawer-content-height-sm);
--_drawer-padding: var(--drawer-content-padding-sm);
}
[data-drawer-content][data-size='md'] {
--_drawer-panel-width: var(--drawer-content-width-md);
--_drawer-panel-height: var(--drawer-content-height-md);
--_drawer-size-width: var(--drawer-content-width-md);
--_drawer-size-height: var(--drawer-content-height-md);
--_drawer-padding: var(--drawer-content-padding-md);
}
[data-drawer-content][data-size='lg'] {
--_drawer-panel-width: var(--drawer-content-width-lg);
--_drawer-panel-height: var(--drawer-content-height-lg);
--_drawer-size-width: var(--drawer-content-width-lg);
--_drawer-size-height: var(--drawer-content-height-lg);
--_drawer-padding: var(--drawer-content-padding-lg);
}
@ -187,8 +193,8 @@
[data-drawer-content][data-variant='overlay'][data-size='full'] {
inset: var(--drawer-content-inset);
width: auto;
height: auto;
width: var(--_drawer-content-width-override, auto);
height: var(--_drawer-content-height-override, auto);
}
/* ── Variant 'inline' — fixed positioning, no backdrop, lower stack ─────── */
@ -238,8 +244,8 @@
[data-drawer-content][data-variant='inline'][data-size='full'] {
inset: var(--drawer-content-inset);
width: auto;
height: auto;
width: var(--_drawer-content-width-override, auto);
height: var(--_drawer-content-height-override, auto);
}
/* ── Variant 'persistent' — in flow, no insets ──────────────────────────── */

@ -80,6 +80,7 @@ export type {
DrawerHeaderProps as HeaderProps,
DrawerFooterProps as FooterProps,
DrawerSize,
DrawerCssLength,
DrawerVariant,
DrawerDirection,
DrawerSide

@ -24,6 +24,8 @@ export type DrawerSize = Extract<Size, 'sm' | 'md' | 'lg' | 'full'>;
export type { DrawerVariant, DrawerDirection, DrawerSide };
export type DrawerCssLength = string | number;
export type DrawerProps = SomaDrawerProviderProps;
export type DrawerTriggerProps = SomaDrawerTriggerProps;
export type DrawerHandleProps = SomaDrawerHandleProps;
@ -43,6 +45,18 @@ export type DrawerContentProps = SomaDrawerContentProps & {
* @default 'md'
*/
size?: ResponsiveProp<DrawerSize>;
/** Width override for left/right drawers. Numbers serialize as px. */
width?: ResponsiveProp<DrawerCssLength>;
/** Minimum inline-size constraint for the panel. Numbers serialize as px. */
minWidth?: ResponsiveProp<DrawerCssLength>;
/** Maximum inline-size constraint for the panel. Numbers serialize as px. */
maxWidth?: ResponsiveProp<DrawerCssLength>;
/** Height override for top/bottom drawers. Numbers serialize as px. */
height?: ResponsiveProp<DrawerCssLength>;
/** Minimum block-size constraint for the panel. Numbers serialize as px. */
minHeight?: ResponsiveProp<DrawerCssLength>;
/** Maximum block-size constraint for the panel. Numbers serialize as px. */
maxHeight?: ResponsiveProp<DrawerCssLength>;
};
export type DrawerOverlayProps = SomaDrawerOverlayProps & {

Loading…
Cancel
Save

Powered by TurnKey Linux.