From 5895f9d8b85639a77b6b88045dba7b3e4a95dcb9 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 17 May 2026 01:56:43 +0200 Subject: [PATCH] Add Eidos progress and meter wrappers --- continue.md | 41 +++++++++++- scripts/eidos-lint-all.ts | 3 +- src/uix/eidos/component-visual-attrs.test.ts | 2 + src/uix/eidos/components/README.md | 4 +- src/uix/eidos/components/meter/index.ts | 27 ++++++++ .../components/meter/meter-indicator.svelte | 10 +++ src/uix/eidos/components/meter/meter.css | 47 ++++++++++++++ src/uix/eidos/components/meter/meter.svelte | 18 ++++++ src/uix/eidos/components/meter/types.ts | 22 +++++++ src/uix/eidos/components/progress/index.ts | 27 ++++++++ .../progress/progress-indicator.svelte | 10 +++ .../eidos/components/progress/progress.css | 63 +++++++++++++++++++ .../eidos/components/progress/progress.svelte | 18 ++++++ src/uix/eidos/components/progress/types.ts | 23 +++++++ src/uix/eidos/generated/base.css | 26 ++++++++ src/uix/eidos/index.css | 2 + src/uix/eidos/lib/recipes/base.ts | 30 +++++++++ 17 files changed, 369 insertions(+), 4 deletions(-) create mode 100644 src/uix/eidos/components/meter/index.ts create mode 100644 src/uix/eidos/components/meter/meter-indicator.svelte create mode 100644 src/uix/eidos/components/meter/meter.css create mode 100644 src/uix/eidos/components/meter/meter.svelte create mode 100644 src/uix/eidos/components/meter/types.ts create mode 100644 src/uix/eidos/components/progress/index.ts create mode 100644 src/uix/eidos/components/progress/progress-indicator.svelte create mode 100644 src/uix/eidos/components/progress/progress.css create mode 100644 src/uix/eidos/components/progress/progress.svelte create mode 100644 src/uix/eidos/components/progress/types.ts diff --git a/continue.md b/continue.md index 9c2746d1b..cd5001178 100644 --- a/continue.md +++ b/continue.md @@ -1,6 +1,45 @@ # Continue -Fecha de corte: 2026-05-16. Rama: `active-uix`. +Fecha de corte: 2026-05-17. Rama: `active-uix`. + +Actualizacion Eidos component migration 2026-05-17: + +- Inicio de la migracion Soma -> Eidos siguiendo `soma/COMPONENT_GUIDE.md` + y `eidos/components/README.md`. +- Componentes migrados: + - `Progress`: root visual `progress.svelte`, `Progress.Indicator`, + `progress.css`, `types.ts` e `index.ts` con disciplined option C. + - `Meter`: root visual `meter.svelte`, `Meter.Indicator`, `meter.css`, + `types.ts` e `index.ts` con disciplined option C. +- Decisiones aplicadas: + - Eidos no crea comportamiento: ambos wrappers envuelven `Provider` e + `Indicator` publicos de Soma. + - La unica prop visual anadida es `size?: ResponsiveProp<'sm' | 'md' | 'lg'>`, + resuelta con `ActiveEidos.resolve(...)` y proyectada como `data-size`. + - `Progress` usa el `data-state` de Soma (`indeterminate`, `loading`, + `loaded`) para cargar/terminar/indeterminado. + - `Meter` usa el `data-state` de Soma (`below`, `optimum`, `above`) para + zonas visuales; no se anade intent ni color en Eidos. + - Los tokens base viven en `THEME_BASE_RECIPE_TOKENS` y se regenero + `src/uix/eidos/generated/base.css`. +- Guardias actualizadas: + - `component-visual-attrs.test.ts` protege `data-size` en `Progress` y + `Meter`. + - `scripts/eidos-lint-all.ts` reconoce `data-floating-wrapper` como marcador + publico actual y retira el sufijo legacy `flat`. + - `components/README.md` refleja `meter` y `progress` como migrados. +- Validado: + - `npx vitest run src/uix/eidos` -> 7 archivos, 84 tests OK. + - `npm run check` -> 0 errores, 0 warnings. + - `node --import tsx/esm scripts/eidos-lint-all.ts` -> 0 invalid, sin drift + hotspots. +- Siguiente bloque sugerido: + - Migrar otro grupo de bajo riesgo desde Soma a Eidos: `separator` no existe + en Soma; candidatos reales por morfo/soma existentes son `slider`, + `number-field`, `pagination` o `rating-group`. + - Antes de tocar componentes grandes (`select`, `combobox`, `calendar`, + `date-field`) conviene fijar una tabla por componente: partes Soma, + props visuales Eidos, recipe tokens y attrs esperados. Actualizacion Eidos components audit 2026-05-17: diff --git a/scripts/eidos-lint-all.ts b/scripts/eidos-lint-all.ts index 5ee17c9ad..3381fd582 100644 --- a/scripts/eidos-lint-all.ts +++ b/scripts/eidos-lint-all.ts @@ -36,7 +36,7 @@ const EIDOS_ONLY_ATTRS = new Set([ 'data-scrollable', 'data-sheet', 'data-size', - 'data-soma-floating-wrapper', + 'data-floating-wrapper', 'data-svg', 'data-svg-direction', 'data-variant' @@ -53,7 +53,6 @@ const RUNTIME_SIGNAL_ATTRS = new Set([ ]) const EIDOS_ONLY_PART_SUFFIXES = [ - 'flat', 'footer', 'handle', 'header', diff --git a/src/uix/eidos/component-visual-attrs.test.ts b/src/uix/eidos/component-visual-attrs.test.ts index f21863a16..611213bb9 100644 --- a/src/uix/eidos/component-visual-attrs.test.ts +++ b/src/uix/eidos/component-visual-attrs.test.ts @@ -16,8 +16,10 @@ const VISUAL_ATTRS = [ ['dialog/dialog-content.svelte', ['data-size', 'data-position', 'data-sheet']], ['drawer/drawer-close.svelte', ['data-icon-only']], ['drawer/drawer-content.svelte', ['data-size']], + ['meter/meter.svelte', ['data-size']], ['popover/popover-close.svelte', ['data-icon-only']], ['popover/popover-content.svelte', ['data-size']], + ['progress/progress.svelte', ['data-size']], ['radio-group/radio-group.svelte', ['data-size', 'data-variant', 'data-color', 'data-columns']], ['switch/switch.svelte', ['data-size']], ['tabs/tabs.svelte', ['data-size', 'data-variant', 'data-fitted', 'data-collapsed', 'data-motion']], diff --git a/src/uix/eidos/components/README.md b/src/uix/eidos/components/README.md index ea53ef2cb..ba4ecc1f9 100644 --- a/src/uix/eidos/components/README.md +++ b/src/uix/eidos/components/README.md @@ -340,7 +340,7 @@ su subdirectorio. --- -## Estado actual de la migración (2026-05-14) +## Estado actual de la migración (2026-05-17) | Componente | Forma canónica | Notas | | ----------- | -------------- | ---------------------------------------------------------------------------- | @@ -354,6 +354,8 @@ su subdirectorio. | accordion | ✅ multi-part | Item, Header, Trigger, Content | | avatar | ✅ multi-part | Image, Fallback (eidos-native) | | icon | ✅ single-part | + 1697 lucide glyphs | +| meter | ✅ multi-part | Indicator | +| progress | ✅ multi-part | Indicator | | tooltip | ✅ multi-part | + Group | | tabs | ✅ multi-part | List, Trigger, Content, Indicator | | checkbox | ✅ multi-part | Indicator, HiddenInput, Group, GroupLabel | diff --git a/src/uix/eidos/components/meter/index.ts b/src/uix/eidos/components/meter/index.ts new file mode 100644 index 000000000..f3830124e --- /dev/null +++ b/src/uix/eidos/components/meter/index.ts @@ -0,0 +1,27 @@ +// Meter — eidos compound API, disciplined option C. +// +// import { Meter } from '$uix/eidos/components/meter'; +// +// +// +// +import MeterRoot from './meter.svelte'; +import Indicator from './meter-indicator.svelte'; + +type MeterNamespace = typeof MeterRoot & { + Indicator: typeof Indicator; +}; + +const Meter = MeterRoot as MeterNamespace; +Meter.Indicator = Indicator; + +export { Meter }; + +export default Meter; + +export type { + MeterProps, + MeterIndicatorProps as IndicatorProps, + MeterSize, + MeterZone +} from './types'; diff --git a/src/uix/eidos/components/meter/meter-indicator.svelte b/src/uix/eidos/components/meter/meter-indicator.svelte new file mode 100644 index 000000000..9a1a963dd --- /dev/null +++ b/src/uix/eidos/components/meter/meter-indicator.svelte @@ -0,0 +1,10 @@ + + + + {@render children?.()} + diff --git a/src/uix/eidos/components/meter/meter.css b/src/uix/eidos/components/meter/meter.css new file mode 100644 index 000000000..c065fb744 --- /dev/null +++ b/src/uix/eidos/components/meter/meter.css @@ -0,0 +1,47 @@ +[data-meter] { + --_meter-height: var(--meter-height-md); + --_meter-radius: var(--meter-radius-md); + --_meter-indicator-bg: var(--meter-indicator-bg-optimum); + + position: relative; + overflow: hidden; + inline-size: 100%; + block-size: var(--_meter-height); + border-radius: var(--_meter-radius); + background: var(--meter-track-bg); +} + +[data-meter][data-size='sm'] { + --_meter-height: var(--meter-height-sm); + --_meter-radius: var(--meter-radius-sm); +} + +[data-meter][data-size='lg'] { + --_meter-height: var(--meter-height-lg); + --_meter-radius: var(--meter-radius-lg); +} + +[data-meter][data-state='below'] { + --_meter-indicator-bg: var(--meter-indicator-bg-below); +} + +[data-meter][data-state='above'] { + --_meter-indicator-bg: var(--meter-indicator-bg-above); +} + +[data-meter-indicator] { + display: block; + inline-size: calc(var(--soma-meter-value-pct, 0) * 1%); + block-size: 100%; + border-radius: inherit; + background: var(--_meter-indicator-bg); + transition: + inline-size var(--meter-transition-duration) var(--meter-transition-ease), + background var(--meter-transition-duration) var(--meter-transition-ease); +} + +@media (prefers-reduced-motion: reduce) { + [data-meter-indicator] { + transition: none; + } +} diff --git a/src/uix/eidos/components/meter/meter.svelte b/src/uix/eidos/components/meter/meter.svelte new file mode 100644 index 000000000..ff266847b --- /dev/null +++ b/src/uix/eidos/components/meter/meter.svelte @@ -0,0 +1,18 @@ + + + + {@render children?.()} + diff --git a/src/uix/eidos/components/meter/types.ts b/src/uix/eidos/components/meter/types.ts new file mode 100644 index 000000000..25874f8b8 --- /dev/null +++ b/src/uix/eidos/components/meter/types.ts @@ -0,0 +1,22 @@ +import type { + IndicatorProps as SomaMeterIndicatorProps, + MeterZone, + ProviderProps as SomaMeterProviderProps +} from '$soma/components/meter'; +import type { ResponsiveProp, Size } from '$uix/eidos/lib/types'; + +export type MeterSize = Extract; + +/** + * Props for the eidos ``. + * + * Extends Soma's meter with visual sizing only. Zone state (`below`, + * `optimum`, `above`) is owned by Soma and emitted through `data-state`. + */ +export type MeterProps = SomaMeterProviderProps & { + /** Visual size. Accepts a responsive map resolved by ActiveEidos. @default 'md' */ + size?: ResponsiveProp; +}; + +export type MeterIndicatorProps = SomaMeterIndicatorProps; +export type { MeterZone }; diff --git a/src/uix/eidos/components/progress/index.ts b/src/uix/eidos/components/progress/index.ts new file mode 100644 index 000000000..c4e607a8e --- /dev/null +++ b/src/uix/eidos/components/progress/index.ts @@ -0,0 +1,27 @@ +// Progress — eidos compound API, disciplined option C. +// +// import { Progress } from '$uix/eidos/components/progress'; +// +// +// +// +import ProgressRoot from './progress.svelte'; +import Indicator from './progress-indicator.svelte'; + +type ProgressNamespace = typeof ProgressRoot & { + Indicator: typeof Indicator; +}; + +const Progress = ProgressRoot as ProgressNamespace; +Progress.Indicator = Indicator; + +export { Progress }; + +export default Progress; + +export type { + ProgressProps, + ProgressIndicatorProps as IndicatorProps, + ProgressSize, + ProgressState +} from './types'; diff --git a/src/uix/eidos/components/progress/progress-indicator.svelte b/src/uix/eidos/components/progress/progress-indicator.svelte new file mode 100644 index 000000000..fd5b5e998 --- /dev/null +++ b/src/uix/eidos/components/progress/progress-indicator.svelte @@ -0,0 +1,10 @@ + + + + {@render children?.()} + diff --git a/src/uix/eidos/components/progress/progress.css b/src/uix/eidos/components/progress/progress.css new file mode 100644 index 000000000..7a65ff75d --- /dev/null +++ b/src/uix/eidos/components/progress/progress.css @@ -0,0 +1,63 @@ +[data-progress] { + --_progress-height: var(--progress-height-md); + --_progress-radius: var(--progress-radius-md); + + position: relative; + overflow: hidden; + inline-size: 100%; + block-size: var(--_progress-height); + border-radius: var(--_progress-radius); + background: var(--progress-track-bg); +} + +[data-progress][data-size='sm'] { + --_progress-height: var(--progress-height-sm); + --_progress-radius: var(--progress-radius-sm); +} + +[data-progress][data-size='lg'] { + --_progress-height: var(--progress-height-lg); + --_progress-radius: var(--progress-radius-lg); +} + +[data-progress-indicator] { + display: block; + inline-size: calc(var(--soma-progress-value-pct, 0) * 1%); + block-size: 100%; + border-radius: inherit; + background: var(--progress-indicator-bg); + transition: + inline-size var(--progress-transition-duration) var(--progress-transition-ease), + background var(--progress-transition-duration) var(--progress-transition-ease); +} + +[data-progress][data-state='loaded'] [data-progress-indicator] { + background: var(--progress-indicator-bg-loaded); +} + +[data-progress][data-state='indeterminate'] [data-progress-indicator] { + inline-size: var(--progress-indeterminate-width); + animation: progress-indeterminate var(--progress-indeterminate-duration) + var(--progress-indeterminate-ease) infinite; +} + +@keyframes progress-indeterminate { + from { + transform: translateX(-100%); + } + + to { + transform: translateX(300%); + } +} + +@media (prefers-reduced-motion: reduce) { + [data-progress-indicator] { + transition: none; + } + + [data-progress][data-state='indeterminate'] [data-progress-indicator] { + animation: none; + transform: none; + } +} diff --git a/src/uix/eidos/components/progress/progress.svelte b/src/uix/eidos/components/progress/progress.svelte new file mode 100644 index 000000000..cc0b5e4ab --- /dev/null +++ b/src/uix/eidos/components/progress/progress.svelte @@ -0,0 +1,18 @@ + + + + {@render children?.()} + diff --git a/src/uix/eidos/components/progress/types.ts b/src/uix/eidos/components/progress/types.ts new file mode 100644 index 000000000..be23c3e52 --- /dev/null +++ b/src/uix/eidos/components/progress/types.ts @@ -0,0 +1,23 @@ +import type { + IndicatorProps as SomaProgressIndicatorProps, + ProgressState, + ProviderProps as SomaProgressProviderProps +} from '$soma/components/progress'; +import type { ResponsiveProp, Size } from '$uix/eidos/lib/types'; + +export type ProgressSize = Extract; + +/** + * Props for the eidos ``. + * + * Extends Soma's progressbar with visual sizing only. Completion state + * (`indeterminate`, `loading`, `loaded`) is owned by Soma and emitted through + * `data-state`. + */ +export type ProgressProps = SomaProgressProviderProps & { + /** Visual size. Accepts a responsive map resolved by ActiveEidos. @default 'md' */ + size?: ResponsiveProp; +}; + +export type ProgressIndicatorProps = SomaProgressIndicatorProps; +export type { ProgressState }; diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 0ddd634d6..3b7600017 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -590,6 +590,18 @@ --drawer-close-disabled-opacity: 0.5; --icon-size: var(--icon-size-md); --icon-stroke-width: var(--icon-stroke-width-md); + --meter-height-sm: 4px; + --meter-height-md: 6px; + --meter-height-lg: 8px; + --meter-radius-sm: var(--radius-full); + --meter-radius-md: var(--radius-full); + --meter-radius-lg: var(--radius-full); + --meter-track-bg: var(--color-neutral-track); + --meter-indicator-bg-below: var(--color-risk-solid); + --meter-indicator-bg-optimum: var(--color-fulfill-solid); + --meter-indicator-bg-above: var(--color-threat-solid); + --meter-transition-duration: var(--duration-normal); + --meter-transition-ease: var(--ease-default); --popover-content-z: 75; --popover-overlay-z: 60; --popover-overlay-bg: var(--color-overlay); @@ -629,6 +641,20 @@ --popover-close-color: var(--color-content-secondary); --popover-close-border: color-mix(in srgb, var(--color-border-default) 72%, transparent); --popover-close-border-hover: var(--color-border-strong); + --progress-height-sm: 4px; + --progress-height-md: 6px; + --progress-height-lg: 8px; + --progress-radius-sm: var(--radius-full); + --progress-radius-md: var(--radius-full); + --progress-radius-lg: var(--radius-full); + --progress-track-bg: var(--color-neutral-track); + --progress-indicator-bg: var(--color-primary-solid); + --progress-indicator-bg-loaded: var(--color-fulfill-solid); + --progress-transition-duration: var(--duration-normal); + --progress-transition-ease: var(--ease-default); + --progress-indeterminate-width: 35%; + --progress-indeterminate-duration: 1.1s; + --progress-indeterminate-ease: var(--ease-default); --radio-group-gap-vertical: var(--space-3); --radio-group-gap-horizontal: var(--space-4); --radio-group-item-size-sm: 16px; diff --git a/src/uix/eidos/index.css b/src/uix/eidos/index.css index ba18b5a97..cf29d269d 100644 --- a/src/uix/eidos/index.css +++ b/src/uix/eidos/index.css @@ -67,6 +67,8 @@ @import './components/drawer/drawer.css'; @import './components/popover/popover.css'; @import './components/tooltip/tooltip.css'; +@import './components/progress/progress.css'; +@import './components/meter/meter.css'; @import './components/toggle/toggle.css'; @import './components/switch/switch.css'; @import './components/collapsible/collapsible.css'; diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 4887185c8..ec2bc08d3 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -333,6 +333,20 @@ export const THEME_BASE_RECIPE_TOKENS = { size: 'var(--icon-size-md)', 'stroke-width': 'var(--icon-stroke-width-md)' }, + meter: { + 'height-sm': '4px', + 'height-md': '6px', + 'height-lg': '8px', + 'radius-sm': 'var(--radius-full)', + 'radius-md': 'var(--radius-full)', + 'radius-lg': 'var(--radius-full)', + 'track-bg': 'var(--color-neutral-track)', + 'indicator-bg-below': 'var(--color-risk-solid)', + 'indicator-bg-optimum': 'var(--color-fulfill-solid)', + 'indicator-bg-above': 'var(--color-threat-solid)', + 'transition-duration': 'var(--duration-normal)', + 'transition-ease': 'var(--ease-default)' + }, popover: { 'content-z': '75', 'overlay-z': '60', @@ -374,6 +388,22 @@ export const THEME_BASE_RECIPE_TOKENS = { 'close-border': 'color-mix(in srgb, var(--color-border-default) 72%, transparent)', 'close-border-hover': 'var(--color-border-strong)' }, + progress: { + 'height-sm': '4px', + 'height-md': '6px', + 'height-lg': '8px', + 'radius-sm': 'var(--radius-full)', + 'radius-md': 'var(--radius-full)', + 'radius-lg': 'var(--radius-full)', + 'track-bg': 'var(--color-neutral-track)', + 'indicator-bg': 'var(--color-primary-solid)', + 'indicator-bg-loaded': 'var(--color-fulfill-solid)', + 'transition-duration': 'var(--duration-normal)', + 'transition-ease': 'var(--ease-default)', + 'indeterminate-width': '35%', + 'indeterminate-duration': '1.1s', + 'indeterminate-ease': 'var(--ease-default)' + }, 'radio-group': { 'gap-vertical': 'var(--space-3)', 'gap-horizontal': 'var(--space-4)',