From a8045658aaab0d317dec269508c1ed72529cc999 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 16 May 2026 19:07:32 +0200 Subject: [PATCH] Guard Eidos recipe CSS contract --- continue.md | 29 +++ src/uix/eidos/README.md | 11 +- src/uix/eidos/components/README.md | 5 +- .../eidos/components/accordion/accordion.css | 6 +- .../components/radio-group/radio-group.css | 4 + src/uix/eidos/components/tabs/tabs.css | 1 + src/uix/eidos/components/toggle/toggle.css | 202 ++++++++---------- src/uix/eidos/components/tooltip/tooltip.css | 4 + src/uix/eidos/generated/base.css | 86 +------- src/uix/eidos/lib/recipes/base.ts | 89 ++------ src/uix/eidos/recipe-css-contract.test.ts | 113 ++++++++++ 11 files changed, 281 insertions(+), 269 deletions(-) create mode 100644 src/uix/eidos/recipe-css-contract.test.ts diff --git a/continue.md b/continue.md index 45c9ff216..c742568ad 100644 --- a/continue.md +++ b/continue.md @@ -128,6 +128,35 @@ Actualizacion Eidos auditoria Kimi 2026-05-16: - `npx vitest run src/uix/eidos` -> 4 archivos, 56 tests OK. - `npm run test` -> 233 archivos, 2270 tests OK. +Actualizacion Eidos recipe contract 2026-05-16: + +- Cerrado el barrido recipe/CSS con una guardia versionada: + `src/uix/eidos/recipe-css-contract.test.ts`. + - Si un CSS de componente consume `--{component}-*`, el alias debe existir + en `THEME_BASE_RECIPE_TOKENS`. + - Si el theme base declara un alias publico, debe estar consumido por el + componente, por sus fuentes Svelte/TS o por otro token compuesto. +- Limpieza aplicada: + - `Toggle` consume sus tokens publicos de variante (`solid`, `outline`, + `ghost`) en vez de duplicar formulas dentro del CSS. Se retira el naming + residual `surface-*` porque la variante publica es `solid`. + - `Checkbox`, `RadioGroup` y `Switch` ya no publican tokens de intents que + sus tipos no pueden activar. + - `Toast` elimina los tokens tipograficos de `message`, parte que la propia + recipe documenta como retirada; conserva `message-color` porque lo usa el + item como color base. + - `Accordion`, `Tabs`, `Tooltip` y `RadioGroup` conectan tokens que estaban + declarados pero no consumidos (`indicator-*`, `list-bg`, `content-z`, + `indicator-bg-checked-disabled`). + - `Icon` mantiene `--icon-size` y `--icon-stroke-width`: son consumidos por + `icon.svelte`, no por CSS. +- Regenerado `src/uix/eidos/generated/base.css` con + `npm run generate:eidos-css`. +- Validacion: + - `npx vitest run src/uix/eidos/recipe-css-contract.test.ts` -> 3 tests OK. + - `npx vitest run src/uix/eidos` -> 5 archivos, 59 tests OK. + - `npm run check` -> 0 errores, 0 warnings. + Actualizacion 2026-05-16: - Barrido de wrappers publicos Soma frente a providers cerrado: diff --git a/src/uix/eidos/README.md b/src/uix/eidos/README.md index 8195182aa..a62de3245 100644 --- a/src/uix/eidos/README.md +++ b/src/uix/eidos/README.md @@ -161,6 +161,13 @@ las recipes CSS consumen. Por ejemplo, `recipes.tooltip['content-z']` genera `--tooltip-content-z`; `recipes.dialog['overlay-bg']` genera `--dialog-overlay-bg`. Asi los componentes mantienen su CSS estable y el tema puede persistir/editar esos valores desde el mismo objeto de authoring. + +El contrato recipe/CSS se valida en `recipe-css-contract.test.ts`: si una +recipe CSS consume `--{component}-*`, ese alias debe existir en +`EidosConfig.recipes`; y si el theme base declara un alias publico, debe estar +consumido por el componente o por otro token compuesto. No hay reservas +fantasma ni paletas que el componente no pueda activar desde su API real. + Para editores de theme, `ActiveEidos.listRecipes()` enumera los componentes con recipe tokens y `ActiveEidos.getRecipeTokens(component)` devuelve una copia defensiva del mapa de aliases. No muta el config interno. @@ -706,6 +713,8 @@ podrá retirarse. estables. - **Recipes**: quedan deliberadamente como `RecipeTokenSet` plano. No se crea jerarquia estructurada hasta que una recipe tenga un builder/consumer real - que necesite mas semantica que aliases CSS. + que necesite mas semantica que aliases CSS. La guardia + `recipe-css-contract.test.ts` evita que el theme base declare aliases no + consumidos o que un CSS de componente use variables fuera del contrato. - **Check del repo**: `npm run check` no reporta errores ni warnings en este punto. diff --git a/src/uix/eidos/components/README.md b/src/uix/eidos/components/README.md index 3d90947cc..d0742d5b6 100644 --- a/src/uix/eidos/components/README.md +++ b/src/uix/eidos/components/README.md @@ -286,11 +286,14 @@ 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) | `--_dialog-padding`, `--_toggle-palette-track` | +| `--_{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. --- diff --git a/src/uix/eidos/components/accordion/accordion.css b/src/uix/eidos/components/accordion/accordion.css index 6a11de5eb..2df2e9d78 100644 --- a/src/uix/eidos/components/accordion/accordion.css +++ b/src/uix/eidos/components/accordion/accordion.css @@ -182,8 +182,8 @@ block-size: var(--_accordion-indicator-size); flex: 0 0 auto; color: var(--accordion-indicator-color); - transition: transform var(--accordion-trigger-transition-duration) - var(--accordion-trigger-transition-ease); + transition: transform var(--accordion-indicator-transition-duration) + var(--accordion-indicator-transition-ease); } [data-accordion-trigger][data-size='sm'] [data-accordion-indicator], @@ -205,7 +205,7 @@ [data-accordion-trigger][data-state='open'] [data-accordion-indicator] { color: var(--accordion-indicator-color-open); - transform: rotate(180deg); + transform: rotate(var(--accordion-indicator-rotation)); } [data-accordion-content] { diff --git a/src/uix/eidos/components/radio-group/radio-group.css b/src/uix/eidos/components/radio-group/radio-group.css index 423816625..8de13fa9b 100644 --- a/src/uix/eidos/components/radio-group/radio-group.css +++ b/src/uix/eidos/components/radio-group/radio-group.css @@ -192,6 +192,10 @@ opacity: var(--radio-group-disabled-opacity); } +[data-radio-group-item][data-disabled][data-state='checked'] { + background: var(--radio-group-indicator-bg-checked-disabled); +} + /* ── Indicator (the dot) ────────────────────────────────────────────────── */ [data-radio-group-indicator] { diff --git a/src/uix/eidos/components/tabs/tabs.css b/src/uix/eidos/components/tabs/tabs.css index e783d68aa..395334925 100644 --- a/src/uix/eidos/components/tabs/tabs.css +++ b/src/uix/eidos/components/tabs/tabs.css @@ -49,6 +49,7 @@ flex-wrap: wrap; gap: var(--tabs-list-gap); min-width: 0; + background: var(--tabs-list-bg); } [data-tabs-list][data-orientation='vertical'] { diff --git a/src/uix/eidos/components/toggle/toggle.css b/src/uix/eidos/components/toggle/toggle.css index 14075e16b..372b3ccb8 100644 --- a/src/uix/eidos/components/toggle/toggle.css +++ b/src/uix/eidos/components/toggle/toggle.css @@ -5,33 +5,24 @@ --_toggle-font-size: var(--toggle-font-size-md); --_toggle-font-weight: var(--toggle-font-weight-md); --_toggle-radius: var(--toggle-radius-md); - --_toggle-palette-track: var(--toggle-color-neutral-track); - --_toggle-palette-element: var(--toggle-color-neutral-element); - --_toggle-palette-hover: var(--toggle-color-neutral-hover); - --_toggle-palette-active: var(--toggle-color-neutral-active); - --_toggle-palette-border: var(--toggle-color-neutral-border); - --_toggle-palette-solid: var(--toggle-color-neutral-solid); - --_toggle-palette-solid-hover: var(--toggle-color-neutral-solid-hover); - --_toggle-palette-text: var(--toggle-color-neutral-text); - --_toggle-palette-contrast: var(--toggle-color-neutral-contrast); - --_toggle-bg: var(--color-surface-raised); - --_toggle-fg: var(--color-content-primary); - --_toggle-border: var(--color-border-default); - --_toggle-hover-bg: color-mix( - in srgb, - var(--color-surface-overlay) 68%, - var(--_toggle-palette-track) - ); - --_toggle-hover-border: color-mix( - in srgb, - var(--_toggle-palette-border) 45%, - var(--color-border-strong) - ); - --_toggle-on-bg: var(--_toggle-palette-element); - --_toggle-on-fg: var(--_toggle-palette-text); - --_toggle-on-border: var(--_toggle-palette-border); - --_toggle-on-hover-bg: var(--_toggle-palette-hover); - --_toggle-on-hover-border: var(--_toggle-palette-border); + --toggle-palette-track: var(--toggle-color-neutral-track); + --toggle-palette-element: var(--toggle-color-neutral-element); + --toggle-palette-hover: var(--toggle-color-neutral-hover); + --toggle-palette-border: var(--toggle-color-neutral-border); + --toggle-palette-solid: var(--toggle-color-neutral-solid); + --toggle-palette-solid-hover: var(--toggle-color-neutral-solid-hover); + --toggle-palette-text: var(--toggle-color-neutral-text); + --toggle-palette-contrast: var(--toggle-color-neutral-contrast); + --_toggle-bg: var(--toggle-solid-bg); + --_toggle-fg: var(--toggle-solid-fg); + --_toggle-border: var(--toggle-solid-border); + --_toggle-hover-bg: var(--toggle-solid-hover-bg); + --_toggle-hover-border: var(--toggle-solid-hover-border); + --_toggle-on-bg: var(--toggle-solid-on-bg); + --_toggle-on-fg: var(--toggle-solid-on-fg); + --_toggle-on-border: var(--toggle-solid-on-border); + --_toggle-on-hover-bg: var(--toggle-solid-on-hover-bg); + --_toggle-on-hover-border: var(--toggle-solid-on-hover-border); display: inline-flex; align-items: center; @@ -59,84 +50,77 @@ } /* ────────────────────────────────────────────────────────────────────── - * data-color: 6 doctrinal values for toggle (subset of the 8 system - * tokens). Maps to existing theme primitives until the global tokens - * are renamed (success → fulfill, warning → risk, danger → threat, - * add secondary + affirm + loss). + * data-color: primary | secondary | neutral | affirm | risk | threat. + * Toggle intentionally excludes fulfill/loss: it is neither completion + * nor irreversible loss; those interactions need a different control. * ────────────────────────────────────────────────────────────────────── */ /* Hierarchy (non-evaluative — applies when intent='neutral') */ [data-toggle][data-color='primary'] { - --_toggle-palette-track: var(--toggle-color-primary-track); - --_toggle-palette-element: var(--toggle-color-primary-element); - --_toggle-palette-hover: var(--toggle-color-primary-hover); - --_toggle-palette-active: var(--toggle-color-primary-active); - --_toggle-palette-border: var(--toggle-color-primary-border); - --_toggle-palette-solid: var(--toggle-color-primary-solid); - --_toggle-palette-solid-hover: var(--toggle-color-primary-solid-hover); - --_toggle-palette-text: var(--toggle-color-primary-text); - --_toggle-palette-contrast: var(--toggle-color-primary-contrast); + --toggle-palette-track: var(--toggle-color-primary-track); + --toggle-palette-element: var(--toggle-color-primary-element); + --toggle-palette-hover: var(--toggle-color-primary-hover); + --toggle-palette-border: var(--toggle-color-primary-border); + --toggle-palette-solid: var(--toggle-color-primary-solid); + --toggle-palette-solid-hover: var(--toggle-color-primary-solid-hover); + --toggle-palette-text: var(--toggle-color-primary-text); + --toggle-palette-contrast: var(--toggle-color-primary-contrast); } [data-toggle][data-color='secondary'] { - --_toggle-palette-track: var(--toggle-color-secondary-track); - --_toggle-palette-element: var(--toggle-color-secondary-element); - --_toggle-palette-hover: var(--toggle-color-secondary-hover); - --_toggle-palette-active: var(--toggle-color-secondary-active); - --_toggle-palette-border: var(--toggle-color-secondary-border); - --_toggle-palette-solid: var(--toggle-color-secondary-solid); - --_toggle-palette-solid-hover: var(--toggle-color-secondary-solid-hover); - --_toggle-palette-text: var(--toggle-color-secondary-text); - --_toggle-palette-contrast: var(--toggle-color-secondary-contrast); + --toggle-palette-track: var(--toggle-color-secondary-track); + --toggle-palette-element: var(--toggle-color-secondary-element); + --toggle-palette-hover: var(--toggle-color-secondary-hover); + --toggle-palette-border: var(--toggle-color-secondary-border); + --toggle-palette-solid: var(--toggle-color-secondary-solid); + --toggle-palette-solid-hover: var(--toggle-color-secondary-solid-hover); + --toggle-palette-text: var(--toggle-color-secondary-text); + --toggle-palette-contrast: var(--toggle-color-secondary-contrast); } [data-toggle][data-color='neutral'] { - --_toggle-palette-track: var(--toggle-color-neutral-track); - --_toggle-palette-element: var(--toggle-color-neutral-element); - --_toggle-palette-hover: var(--toggle-color-neutral-hover); - --_toggle-palette-active: var(--toggle-color-neutral-active); - --_toggle-palette-border: var(--toggle-color-neutral-border); - --_toggle-palette-solid: var(--toggle-color-neutral-solid); - --_toggle-palette-solid-hover: var(--toggle-color-neutral-solid-hover); - --_toggle-palette-text: var(--toggle-color-neutral-text); - --_toggle-palette-contrast: var(--toggle-color-neutral-contrast); + --toggle-palette-track: var(--toggle-color-neutral-track); + --toggle-palette-element: var(--toggle-color-neutral-element); + --toggle-palette-hover: var(--toggle-color-neutral-hover); + --toggle-palette-border: var(--toggle-color-neutral-border); + --toggle-palette-solid: var(--toggle-color-neutral-solid); + --toggle-palette-solid-hover: var(--toggle-color-neutral-solid-hover); + --toggle-palette-text: var(--toggle-color-neutral-text); + --toggle-palette-contrast: var(--toggle-color-neutral-contrast); } /* Intent (evaluative — wins over hierarchy) */ [data-toggle][data-color='affirm'] { - --_toggle-palette-track: var(--toggle-color-affirm-track); - --_toggle-palette-element: var(--toggle-color-affirm-element); - --_toggle-palette-hover: var(--toggle-color-affirm-hover); - --_toggle-palette-active: var(--toggle-color-affirm-active); - --_toggle-palette-border: var(--toggle-color-affirm-border); - --_toggle-palette-solid: var(--toggle-color-affirm-solid); - --_toggle-palette-solid-hover: var(--toggle-color-affirm-solid-hover); - --_toggle-palette-text: var(--toggle-color-affirm-text); - --_toggle-palette-contrast: var(--toggle-color-affirm-contrast); + --toggle-palette-track: var(--toggle-color-affirm-track); + --toggle-palette-element: var(--toggle-color-affirm-element); + --toggle-palette-hover: var(--toggle-color-affirm-hover); + --toggle-palette-border: var(--toggle-color-affirm-border); + --toggle-palette-solid: var(--toggle-color-affirm-solid); + --toggle-palette-solid-hover: var(--toggle-color-affirm-solid-hover); + --toggle-palette-text: var(--toggle-color-affirm-text); + --toggle-palette-contrast: var(--toggle-color-affirm-contrast); } [data-toggle][data-color='risk'] { - --_toggle-palette-track: var(--toggle-color-risk-track); - --_toggle-palette-element: var(--toggle-color-risk-element); - --_toggle-palette-hover: var(--toggle-color-risk-hover); - --_toggle-palette-active: var(--toggle-color-risk-active); - --_toggle-palette-border: var(--toggle-color-risk-border); - --_toggle-palette-solid: var(--toggle-color-risk-solid); - --_toggle-palette-solid-hover: var(--toggle-color-risk-solid-hover); - --_toggle-palette-text: var(--toggle-color-risk-text); - --_toggle-palette-contrast: var(--toggle-color-risk-contrast); + --toggle-palette-track: var(--toggle-color-risk-track); + --toggle-palette-element: var(--toggle-color-risk-element); + --toggle-palette-hover: var(--toggle-color-risk-hover); + --toggle-palette-border: var(--toggle-color-risk-border); + --toggle-palette-solid: var(--toggle-color-risk-solid); + --toggle-palette-solid-hover: var(--toggle-color-risk-solid-hover); + --toggle-palette-text: var(--toggle-color-risk-text); + --toggle-palette-contrast: var(--toggle-color-risk-contrast); } [data-toggle][data-color='threat'] { - --_toggle-palette-track: var(--toggle-color-threat-track); - --_toggle-palette-element: var(--toggle-color-threat-element); - --_toggle-palette-hover: var(--toggle-color-threat-hover); - --_toggle-palette-active: var(--toggle-color-threat-active); - --_toggle-palette-border: var(--toggle-color-threat-border); - --_toggle-palette-solid: var(--toggle-color-threat-solid); - --_toggle-palette-solid-hover: var(--toggle-color-threat-solid-hover); - --_toggle-palette-text: var(--toggle-color-threat-text); - --_toggle-palette-contrast: var(--toggle-color-threat-contrast); + --toggle-palette-track: var(--toggle-color-threat-track); + --toggle-palette-element: var(--toggle-color-threat-element); + --toggle-palette-hover: var(--toggle-color-threat-hover); + --toggle-palette-border: var(--toggle-color-threat-border); + --toggle-palette-solid: var(--toggle-color-threat-solid); + --toggle-palette-solid-hover: var(--toggle-color-threat-solid-hover); + --toggle-palette-text: var(--toggle-color-threat-text); + --toggle-palette-contrast: var(--toggle-color-threat-contrast); } [data-toggle][data-size='sm'] { @@ -158,37 +142,29 @@ } [data-toggle][data-variant='outline'] { - --_toggle-bg: transparent; - --_toggle-fg: var(--_toggle-palette-text); - --_toggle-border: color-mix( - in srgb, - var(--_toggle-palette-border) 88%, - var(--color-border-default) - ); - --_toggle-hover-bg: var(--_toggle-palette-track); - --_toggle-hover-border: var(--_toggle-palette-border); - --_toggle-on-bg: var(--_toggle-palette-element); - --_toggle-on-fg: var(--_toggle-palette-text); - --_toggle-on-border: var(--_toggle-palette-border); - --_toggle-on-hover-bg: var(--_toggle-palette-hover); - --_toggle-on-hover-border: var(--_toggle-palette-border); + --_toggle-bg: var(--toggle-outline-bg); + --_toggle-fg: var(--toggle-outline-fg); + --_toggle-border: var(--toggle-outline-border); + --_toggle-hover-bg: var(--toggle-outline-hover-bg); + --_toggle-hover-border: var(--toggle-outline-hover-border); + --_toggle-on-bg: var(--toggle-outline-on-bg); + --_toggle-on-fg: var(--toggle-outline-on-fg); + --_toggle-on-border: var(--toggle-outline-on-border); + --_toggle-on-hover-bg: var(--toggle-outline-on-hover-bg); + --_toggle-on-hover-border: var(--toggle-outline-on-hover-border); } [data-toggle][data-variant='ghost'] { - --_toggle-bg: transparent; - --_toggle-fg: var(--_toggle-palette-text); - --_toggle-border: transparent; - --_toggle-hover-bg: color-mix(in srgb, var(--_toggle-palette-track) 72%, transparent); - --_toggle-hover-border: transparent; - --_toggle-on-bg: var(--_toggle-palette-track); - --_toggle-on-fg: var(--_toggle-palette-text); - --_toggle-on-border: color-mix(in srgb, var(--_toggle-palette-border) 72%, transparent); - --_toggle-on-hover-bg: var(--_toggle-palette-element); - --_toggle-on-hover-border: color-mix( - in srgb, - var(--_toggle-palette-border) 88%, - transparent - ); + --_toggle-bg: var(--toggle-ghost-bg); + --_toggle-fg: var(--toggle-ghost-fg); + --_toggle-border: var(--toggle-ghost-border); + --_toggle-hover-bg: var(--toggle-ghost-hover-bg); + --_toggle-hover-border: var(--toggle-ghost-hover-border); + --_toggle-on-bg: var(--toggle-ghost-on-bg); + --_toggle-on-fg: var(--toggle-ghost-on-fg); + --_toggle-on-border: var(--toggle-ghost-on-border); + --_toggle-on-hover-bg: var(--toggle-ghost-on-hover-bg); + --_toggle-on-hover-border: var(--toggle-ghost-on-hover-border); } [data-toggle]:hover { diff --git a/src/uix/eidos/components/tooltip/tooltip.css b/src/uix/eidos/components/tooltip/tooltip.css index 5529b40ad..ba205b75d 100644 --- a/src/uix/eidos/components/tooltip/tooltip.css +++ b/src/uix/eidos/components/tooltip/tooltip.css @@ -1,5 +1,9 @@ /* ── Content (variant-shared base) ──────────────────────────────────────── */ +[data-soma-floating-wrapper]:has(> [data-tooltip-content]) { + z-index: var(--tooltip-content-z); +} + [data-tooltip-content] { --_tooltip-py: var(--tooltip-padding-y-md); --_tooltip-px: var(--tooltip-padding-x-md); diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 0d6f137a8..1391aa04e 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -418,26 +418,19 @@ --checkbox-size-sm-box-size: 16px; --checkbox-size-sm-indicator-size: 10px; --checkbox-size-sm-indicator-stroke: 2px; - --checkbox-size-sm-indeterminate-width: 8px; - --checkbox-size-sm-indeterminate-height: 2px; --checkbox-size-md-box-size: 18px; --checkbox-size-md-indicator-size: 12px; --checkbox-size-md-indicator-stroke: 2px; - --checkbox-size-md-indeterminate-width: 10px; - --checkbox-size-md-indeterminate-height: 2px; --checkbox-size-lg-box-size: 22px; --checkbox-size-lg-indicator-size: 14px; --checkbox-size-lg-indicator-stroke: 2px; - --checkbox-size-lg-indeterminate-width: 12px; - --checkbox-size-lg-indeterminate-height: 2px; --checkbox-radius: var(--radius-sm); --checkbox-border-width: var(--border-width); - --checkbox-indeterminate-radius: var(--radius-full); --checkbox-focus-ring-offset: var(--focus-ring-offset); --checkbox-focus-ring-width: var(--focus-ring-width); --checkbox-focus-ring-color: var(--focus-ring-color); --checkbox-focus-ring-surface: var(--color-surface-default); - --checkbox-focus-ring: var(--focus-ring); + --checkbox-focus-ring: 0 0 0 var(--checkbox-focus-ring-offset) var(--checkbox-focus-ring-surface), 0 0 0 calc(var(--checkbox-focus-ring-offset) + var(--checkbox-focus-ring-width)) var(--checkbox-focus-ring-color); --checkbox-transition-duration: var(--duration-fast); --checkbox-transition-ease: var(--ease-default); --checkbox-stroke-duration: 220ms; @@ -455,18 +448,10 @@ --checkbox-color-primary-solid-hover: var(--color-primary-solid-hover); --checkbox-color-neutral-solid: var(--color-neutral-solid); --checkbox-color-neutral-solid-hover: var(--color-neutral-solid-hover); - --checkbox-color-fulfill-solid: var(--color-fulfill-solid); - --checkbox-color-fulfill-solid-hover: var(--color-fulfill-solid-hover); - --checkbox-color-risk-solid: var(--color-risk-solid); - --checkbox-color-risk-solid-hover: var(--color-risk-solid-hover); - --checkbox-color-threat-solid: var(--color-threat-solid); --checkbox-color-secondary-solid: var(--color-secondary-solid); --checkbox-color-secondary-solid-hover: var(--color-secondary-solid-hover); --checkbox-color-affirm-solid: var(--color-affirm-solid); --checkbox-color-affirm-solid-hover: var(--color-affirm-solid-hover); - --checkbox-color-loss-solid: var(--color-loss-solid); - --checkbox-color-loss-solid-hover: var(--color-loss-solid-hover); - --checkbox-color-threat-solid-hover: var(--color-threat-solid-hover); --checkbox-group-gap: var(--space-3); --checkbox-group-label-font-family: var(--font-ui); --checkbox-group-label-font-size: var(--font-size-sm); @@ -645,9 +630,6 @@ --radio-group-item-size-sm: 16px; --radio-group-item-size-md: 18px; --radio-group-item-size-lg: 22px; - --radio-group-indicator-size-sm: 16px; - --radio-group-indicator-size-md: 18px; - --radio-group-indicator-size-lg: 22px; --radio-group-dot-size-sm: 6px; --radio-group-dot-size-md: 7px; --radio-group-dot-size-lg: 9px; @@ -668,7 +650,6 @@ --radio-group-indicator-bg-disabled: color-mix( in srgb, var(--color-surface-overlay) 78%, var(--color-surface-default) ); --radio-group-indicator-border-disabled: color-mix( in srgb, var(--color-border-default) 72%, var(--color-border-subtle) ); --radio-group-indicator-bg-checked-disabled: color-mix( in srgb, var(--color-content-disabled) 40%, var(--color-surface-overlay) ); - --radio-group-dot-radius: var(--radius-full); --radio-group-dot-color: var(--color-content-on-solid); --radio-group-dot-color-disabled: color-mix( in srgb, var(--color-content-on-solid) 82%, var(--color-surface-default) ); --radio-group-focus-ring-offset: var(--focus-ring-offset); @@ -683,18 +664,10 @@ --radio-group-color-primary-solid-hover: var(--color-primary-solid-hover); --radio-group-color-neutral-solid: var(--color-neutral-solid); --radio-group-color-neutral-solid-hover: var(--color-neutral-solid-hover); - --radio-group-color-fulfill-solid: var(--color-fulfill-solid); - --radio-group-color-fulfill-solid-hover: var(--color-fulfill-solid-hover); - --radio-group-color-risk-solid: var(--color-risk-solid); - --radio-group-color-risk-solid-hover: var(--color-risk-solid-hover); - --radio-group-color-threat-solid: var(--color-threat-solid); --radio-group-color-secondary-solid: var(--color-secondary-solid); --radio-group-color-secondary-solid-hover: var(--color-secondary-solid-hover); --radio-group-color-affirm-solid: var(--color-affirm-solid); --radio-group-color-affirm-solid-hover: var(--color-affirm-solid-hover); - --radio-group-color-loss-solid: var(--color-loss-solid); - --radio-group-color-loss-solid-hover: var(--color-loss-solid-hover); - --radio-group-color-threat-solid-hover: var(--color-threat-solid-hover); --radio-group-label-font-family: var(--font-ui); --radio-group-label-font-size: var(--font-size-sm); --radio-group-label-font-weight: var(--font-weight-medium); @@ -743,10 +716,6 @@ --switch-color-neutral-border: var(--color-neutral-border); --switch-color-neutral-solid: var(--color-neutral-solid); --switch-color-neutral-solid-hover: var(--color-neutral-solid-hover); - --switch-color-fulfill-track: var(--color-fulfill-track); - --switch-color-fulfill-border: var(--color-fulfill-border); - --switch-color-fulfill-solid: var(--color-fulfill-solid); - --switch-color-fulfill-solid-hover: var(--color-fulfill-solid-hover); --switch-color-risk-track: var(--color-risk-track); --switch-color-risk-border: var(--color-risk-border); --switch-color-risk-solid: var(--color-risk-solid); @@ -763,10 +732,6 @@ --switch-color-affirm-border: var(--color-affirm-border); --switch-color-affirm-solid: var(--color-affirm-solid); --switch-color-affirm-solid-hover: var(--color-affirm-solid-hover); - --switch-color-loss-track: var(--color-loss-track); - --switch-color-loss-border: var(--color-loss-border); - --switch-color-loss-solid: var(--color-loss-solid); - --switch-color-loss-solid-hover: var(--color-loss-solid-hover); --tabs-stack-gap: var(--space-4); --tabs-line-stack-gap: var(--space-3); --tabs-provider-radius: var(--radius-xl); @@ -863,10 +828,6 @@ --toast-title-font-weight: var(--font-weight-semibold); --toast-title-line-height: var(--leading-ui); --toast-title-color: var(--color-content-primary); - --toast-message-font-family: var(--font-ui); - --toast-message-font-size: var(--font-size-sm); - --toast-message-font-weight: var(--font-weight-medium); - --toast-message-line-height: var(--leading-prose); --toast-message-color: var(--color-content-primary); --toast-description-font-family: var(--font-ui); --toast-description-font-size: var(--font-size-xs); @@ -967,7 +928,6 @@ --toggle-color-primary-track: var(--color-primary-track); --toggle-color-primary-element: var(--color-primary-element); --toggle-color-primary-hover: var(--color-primary-hover); - --toggle-color-primary-active: var(--color-primary-active); --toggle-color-primary-border: var(--color-primary-border); --toggle-color-primary-solid: var(--color-primary-solid); --toggle-color-primary-solid-hover: var(--color-primary-solid-hover); @@ -976,25 +936,14 @@ --toggle-color-neutral-track: var(--color-neutral-track); --toggle-color-neutral-element: var(--color-neutral-element); --toggle-color-neutral-hover: var(--color-neutral-hover); - --toggle-color-neutral-active: var(--color-neutral-active); --toggle-color-neutral-border: var(--color-neutral-border); --toggle-color-neutral-solid: var(--color-neutral-solid); --toggle-color-neutral-solid-hover: var(--color-neutral-solid-hover); --toggle-color-neutral-text: var(--color-neutral-text); --toggle-color-neutral-contrast: var(--color-neutral-contrast); - --toggle-color-fulfill-track: var(--color-fulfill-track); - --toggle-color-fulfill-element: var(--color-fulfill-element); - --toggle-color-fulfill-hover: var(--color-fulfill-hover); - --toggle-color-fulfill-active: var(--color-fulfill-active); - --toggle-color-fulfill-border: var(--color-fulfill-border); - --toggle-color-fulfill-solid: var(--color-fulfill-solid); - --toggle-color-fulfill-solid-hover: var(--color-fulfill-solid-hover); - --toggle-color-fulfill-text: var(--color-fulfill-text); - --toggle-color-fulfill-contrast: var(--color-fulfill-contrast); --toggle-color-risk-track: var(--color-risk-track); --toggle-color-risk-element: var(--color-risk-element); --toggle-color-risk-hover: var(--color-risk-hover); - --toggle-color-risk-active: var(--color-risk-active); --toggle-color-risk-border: var(--color-risk-border); --toggle-color-risk-solid: var(--color-risk-solid); --toggle-color-risk-solid-hover: var(--color-risk-solid-hover); @@ -1003,7 +952,6 @@ --toggle-color-threat-track: var(--color-threat-track); --toggle-color-threat-element: var(--color-threat-element); --toggle-color-threat-hover: var(--color-threat-hover); - --toggle-color-threat-active: var(--color-threat-active); --toggle-color-threat-border: var(--color-threat-border); --toggle-color-threat-solid: var(--color-threat-solid); --toggle-color-threat-solid-hover: var(--color-threat-solid-hover); @@ -1012,7 +960,6 @@ --toggle-color-secondary-track: var(--color-secondary-track); --toggle-color-secondary-element: var(--color-secondary-element); --toggle-color-secondary-hover: var(--color-secondary-hover); - --toggle-color-secondary-active: var(--color-secondary-active); --toggle-color-secondary-border: var(--color-secondary-border); --toggle-color-secondary-solid: var(--color-secondary-solid); --toggle-color-secondary-solid-hover: var(--color-secondary-solid-hover); @@ -1021,40 +968,29 @@ --toggle-color-affirm-track: var(--color-affirm-track); --toggle-color-affirm-element: var(--color-affirm-element); --toggle-color-affirm-hover: var(--color-affirm-hover); - --toggle-color-affirm-active: var(--color-affirm-active); --toggle-color-affirm-border: var(--color-affirm-border); --toggle-color-affirm-solid: var(--color-affirm-solid); --toggle-color-affirm-solid-hover: var(--color-affirm-solid-hover); --toggle-color-affirm-text: var(--color-affirm-text); --toggle-color-affirm-contrast: var(--color-affirm-contrast); - --toggle-color-loss-track: var(--color-loss-track); - --toggle-color-loss-element: var(--color-loss-element); - --toggle-color-loss-hover: var(--color-loss-hover); - --toggle-color-loss-active: var(--color-loss-active); - --toggle-color-loss-border: var(--color-loss-border); - --toggle-color-loss-solid: var(--color-loss-solid); - --toggle-color-loss-solid-hover: var(--color-loss-solid-hover); - --toggle-color-loss-text: var(--color-loss-text); - --toggle-color-loss-contrast: var(--color-loss-contrast); --toggle-palette-track: var(--toggle-color-neutral-track); --toggle-palette-element: var(--toggle-color-neutral-element); --toggle-palette-hover: var(--toggle-color-neutral-hover); - --toggle-palette-active: var(--toggle-color-neutral-active); --toggle-palette-border: var(--toggle-color-neutral-border); --toggle-palette-solid: var(--toggle-color-neutral-solid); --toggle-palette-solid-hover: var(--toggle-color-neutral-solid-hover); --toggle-palette-text: var(--toggle-color-neutral-text); --toggle-palette-contrast: var(--toggle-color-neutral-contrast); - --toggle-surface-bg: var(--color-surface-raised); - --toggle-surface-fg: var(--color-content-primary); - --toggle-surface-border: var(--color-border-default); - --toggle-surface-hover-bg: color-mix( in srgb, var(--color-surface-overlay) 68%, var(--toggle-palette-track) ); - --toggle-surface-hover-border: color-mix( in srgb, var(--toggle-palette-border) 45%, var(--color-border-strong) ); - --toggle-surface-on-bg: var(--toggle-palette-element); - --toggle-surface-on-fg: var(--toggle-palette-text); - --toggle-surface-on-border: var(--toggle-palette-border); - --toggle-surface-on-hover-bg: var(--toggle-palette-hover); - --toggle-surface-on-hover-border: var(--toggle-palette-border); + --toggle-solid-bg: var(--color-surface-raised); + --toggle-solid-fg: var(--color-content-primary); + --toggle-solid-border: var(--color-border-default); + --toggle-solid-hover-bg: color-mix( in srgb, var(--color-surface-overlay) 68%, var(--toggle-palette-track) ); + --toggle-solid-hover-border: color-mix( in srgb, var(--toggle-palette-border) 45%, var(--color-border-strong) ); + --toggle-solid-on-bg: var(--toggle-palette-solid); + --toggle-solid-on-fg: var(--toggle-palette-contrast); + --toggle-solid-on-border: var(--toggle-palette-solid); + --toggle-solid-on-hover-bg: var(--toggle-palette-solid-hover); + --toggle-solid-on-hover-border: var(--toggle-palette-solid-hover); --toggle-outline-bg: transparent; --toggle-outline-fg: var(--toggle-palette-text); --toggle-outline-border: color-mix( in srgb, var(--toggle-palette-border) 88%, var(--color-border-default) ); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index f086802df..0fd7a9809 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -146,26 +146,20 @@ export const THEME_BASE_RECIPE_TOKENS = { 'size-sm-box-size': '16px', 'size-sm-indicator-size': '10px', 'size-sm-indicator-stroke': '2px', - 'size-sm-indeterminate-width': '8px', - 'size-sm-indeterminate-height': '2px', 'size-md-box-size': '18px', 'size-md-indicator-size': '12px', 'size-md-indicator-stroke': '2px', - 'size-md-indeterminate-width': '10px', - 'size-md-indeterminate-height': '2px', 'size-lg-box-size': '22px', 'size-lg-indicator-size': '14px', 'size-lg-indicator-stroke': '2px', - 'size-lg-indeterminate-width': '12px', - 'size-lg-indeterminate-height': '2px', radius: 'var(--radius-sm)', 'border-width': 'var(--border-width)', - 'indeterminate-radius': 'var(--radius-full)', 'focus-ring-offset': 'var(--focus-ring-offset)', 'focus-ring-width': 'var(--focus-ring-width)', 'focus-ring-color': 'var(--focus-ring-color)', 'focus-ring-surface': 'var(--color-surface-default)', - 'focus-ring': 'var(--focus-ring)', + 'focus-ring': + '0 0 0 var(--checkbox-focus-ring-offset) var(--checkbox-focus-ring-surface), 0 0 0 calc(var(--checkbox-focus-ring-offset) + var(--checkbox-focus-ring-width)) var(--checkbox-focus-ring-color)', 'transition-duration': 'var(--duration-fast)', 'transition-ease': 'var(--ease-default)', 'stroke-duration': '220ms', @@ -187,18 +181,10 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-primary-solid-hover': 'var(--color-primary-solid-hover)', 'color-neutral-solid': 'var(--color-neutral-solid)', 'color-neutral-solid-hover': 'var(--color-neutral-solid-hover)', - 'color-fulfill-solid': 'var(--color-fulfill-solid)', - 'color-fulfill-solid-hover': 'var(--color-fulfill-solid-hover)', - 'color-risk-solid': 'var(--color-risk-solid)', - 'color-risk-solid-hover': 'var(--color-risk-solid-hover)', - 'color-threat-solid': 'var(--color-threat-solid)', 'color-secondary-solid': 'var(--color-secondary-solid)', 'color-secondary-solid-hover': 'var(--color-secondary-solid-hover)', 'color-affirm-solid': 'var(--color-affirm-solid)', 'color-affirm-solid-hover': 'var(--color-affirm-solid-hover)', - 'color-loss-solid': 'var(--color-loss-solid)', - 'color-loss-solid-hover': 'var(--color-loss-solid-hover)', - 'color-threat-solid-hover': 'var(--color-threat-solid-hover)', 'group-gap': 'var(--space-3)', 'group-label-font-family': 'var(--font-ui)', 'group-label-font-size': 'var(--font-size-sm)', @@ -390,9 +376,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'item-size-sm': '16px', 'item-size-md': '18px', 'item-size-lg': '22px', - 'indicator-size-sm': '16px', - 'indicator-size-md': '18px', - 'indicator-size-lg': '22px', 'dot-size-sm': '6px', 'dot-size-md': '7px', 'dot-size-lg': '9px', @@ -416,7 +399,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-mix( in srgb, var(--color-border-default) 72%, var(--color-border-subtle) )', 'indicator-bg-checked-disabled': 'color-mix( in srgb, var(--color-content-disabled) 40%, var(--color-surface-overlay) )', - 'dot-radius': 'var(--radius-full)', 'dot-color': 'var(--color-content-on-solid)', 'dot-color-disabled': 'color-mix( in srgb, var(--color-content-on-solid) 82%, var(--color-surface-default) )', @@ -433,18 +415,10 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-primary-solid-hover': 'var(--color-primary-solid-hover)', 'color-neutral-solid': 'var(--color-neutral-solid)', 'color-neutral-solid-hover': 'var(--color-neutral-solid-hover)', - 'color-fulfill-solid': 'var(--color-fulfill-solid)', - 'color-fulfill-solid-hover': 'var(--color-fulfill-solid-hover)', - 'color-risk-solid': 'var(--color-risk-solid)', - 'color-risk-solid-hover': 'var(--color-risk-solid-hover)', - 'color-threat-solid': 'var(--color-threat-solid)', 'color-secondary-solid': 'var(--color-secondary-solid)', 'color-secondary-solid-hover': 'var(--color-secondary-solid-hover)', 'color-affirm-solid': 'var(--color-affirm-solid)', 'color-affirm-solid-hover': 'var(--color-affirm-solid-hover)', - 'color-loss-solid': 'var(--color-loss-solid)', - 'color-loss-solid-hover': 'var(--color-loss-solid-hover)', - 'color-threat-solid-hover': 'var(--color-threat-solid-hover)', 'label-font-family': 'var(--font-ui)', 'label-font-size': 'var(--font-size-sm)', 'label-font-weight': 'var(--font-weight-medium)', @@ -501,10 +475,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-neutral-border': 'var(--color-neutral-border)', 'color-neutral-solid': 'var(--color-neutral-solid)', 'color-neutral-solid-hover': 'var(--color-neutral-solid-hover)', - 'color-fulfill-track': 'var(--color-fulfill-track)', - 'color-fulfill-border': 'var(--color-fulfill-border)', - 'color-fulfill-solid': 'var(--color-fulfill-solid)', - 'color-fulfill-solid-hover': 'var(--color-fulfill-solid-hover)', 'color-risk-track': 'var(--color-risk-track)', 'color-risk-border': 'var(--color-risk-border)', 'color-risk-solid': 'var(--color-risk-solid)', @@ -520,11 +490,7 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-affirm-track': 'var(--color-affirm-track)', 'color-affirm-border': 'var(--color-affirm-border)', 'color-affirm-solid': 'var(--color-affirm-solid)', - 'color-affirm-solid-hover': 'var(--color-affirm-solid-hover)', - 'color-loss-track': 'var(--color-loss-track)', - 'color-loss-border': 'var(--color-loss-border)', - 'color-loss-solid': 'var(--color-loss-solid)', - 'color-loss-solid-hover': 'var(--color-loss-solid-hover)' + 'color-affirm-solid-hover': 'var(--color-affirm-solid-hover)' }, tabs: { 'stack-gap': 'var(--space-4)', @@ -627,10 +593,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'title-font-weight': 'var(--font-weight-semibold)', 'title-line-height': 'var(--leading-ui)', 'title-color': 'var(--color-content-primary)', - 'message-font-family': 'var(--font-ui)', - 'message-font-size': 'var(--font-size-sm)', - 'message-font-weight': 'var(--font-weight-medium)', - 'message-line-height': 'var(--leading-prose)', 'message-color': 'var(--color-content-primary)', 'description-font-family': 'var(--font-ui)', 'description-font-size': 'var(--font-size-xs)', @@ -745,7 +707,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-primary-track': 'var(--color-primary-track)', 'color-primary-element': 'var(--color-primary-element)', 'color-primary-hover': 'var(--color-primary-hover)', - 'color-primary-active': 'var(--color-primary-active)', 'color-primary-border': 'var(--color-primary-border)', 'color-primary-solid': 'var(--color-primary-solid)', 'color-primary-solid-hover': 'var(--color-primary-solid-hover)', @@ -754,25 +715,14 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-neutral-track': 'var(--color-neutral-track)', 'color-neutral-element': 'var(--color-neutral-element)', 'color-neutral-hover': 'var(--color-neutral-hover)', - 'color-neutral-active': 'var(--color-neutral-active)', 'color-neutral-border': 'var(--color-neutral-border)', 'color-neutral-solid': 'var(--color-neutral-solid)', 'color-neutral-solid-hover': 'var(--color-neutral-solid-hover)', 'color-neutral-text': 'var(--color-neutral-text)', 'color-neutral-contrast': 'var(--color-neutral-contrast)', - 'color-fulfill-track': 'var(--color-fulfill-track)', - 'color-fulfill-element': 'var(--color-fulfill-element)', - 'color-fulfill-hover': 'var(--color-fulfill-hover)', - 'color-fulfill-active': 'var(--color-fulfill-active)', - 'color-fulfill-border': 'var(--color-fulfill-border)', - 'color-fulfill-solid': 'var(--color-fulfill-solid)', - 'color-fulfill-solid-hover': 'var(--color-fulfill-solid-hover)', - 'color-fulfill-text': 'var(--color-fulfill-text)', - 'color-fulfill-contrast': 'var(--color-fulfill-contrast)', 'color-risk-track': 'var(--color-risk-track)', 'color-risk-element': 'var(--color-risk-element)', 'color-risk-hover': 'var(--color-risk-hover)', - 'color-risk-active': 'var(--color-risk-active)', 'color-risk-border': 'var(--color-risk-border)', 'color-risk-solid': 'var(--color-risk-solid)', 'color-risk-solid-hover': 'var(--color-risk-solid-hover)', @@ -781,7 +731,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-threat-track': 'var(--color-threat-track)', 'color-threat-element': 'var(--color-threat-element)', 'color-threat-hover': 'var(--color-threat-hover)', - 'color-threat-active': 'var(--color-threat-active)', 'color-threat-border': 'var(--color-threat-border)', 'color-threat-solid': 'var(--color-threat-solid)', 'color-threat-solid-hover': 'var(--color-threat-solid-hover)', @@ -790,7 +739,6 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-secondary-track': 'var(--color-secondary-track)', 'color-secondary-element': 'var(--color-secondary-element)', 'color-secondary-hover': 'var(--color-secondary-hover)', - 'color-secondary-active': 'var(--color-secondary-active)', 'color-secondary-border': 'var(--color-secondary-border)', 'color-secondary-solid': 'var(--color-secondary-solid)', 'color-secondary-solid-hover': 'var(--color-secondary-solid-hover)', @@ -799,42 +747,31 @@ export const THEME_BASE_RECIPE_TOKENS = { 'color-affirm-track': 'var(--color-affirm-track)', 'color-affirm-element': 'var(--color-affirm-element)', 'color-affirm-hover': 'var(--color-affirm-hover)', - 'color-affirm-active': 'var(--color-affirm-active)', 'color-affirm-border': 'var(--color-affirm-border)', 'color-affirm-solid': 'var(--color-affirm-solid)', 'color-affirm-solid-hover': 'var(--color-affirm-solid-hover)', 'color-affirm-text': 'var(--color-affirm-text)', 'color-affirm-contrast': 'var(--color-affirm-contrast)', - 'color-loss-track': 'var(--color-loss-track)', - 'color-loss-element': 'var(--color-loss-element)', - 'color-loss-hover': 'var(--color-loss-hover)', - 'color-loss-active': 'var(--color-loss-active)', - 'color-loss-border': 'var(--color-loss-border)', - 'color-loss-solid': 'var(--color-loss-solid)', - 'color-loss-solid-hover': 'var(--color-loss-solid-hover)', - 'color-loss-text': 'var(--color-loss-text)', - 'color-loss-contrast': 'var(--color-loss-contrast)', 'palette-track': 'var(--toggle-color-neutral-track)', 'palette-element': 'var(--toggle-color-neutral-element)', 'palette-hover': 'var(--toggle-color-neutral-hover)', - 'palette-active': 'var(--toggle-color-neutral-active)', 'palette-border': 'var(--toggle-color-neutral-border)', 'palette-solid': 'var(--toggle-color-neutral-solid)', 'palette-solid-hover': 'var(--toggle-color-neutral-solid-hover)', 'palette-text': 'var(--toggle-color-neutral-text)', 'palette-contrast': 'var(--toggle-color-neutral-contrast)', - 'surface-bg': 'var(--color-surface-raised)', - 'surface-fg': 'var(--color-content-primary)', - 'surface-border': 'var(--color-border-default)', - 'surface-hover-bg': + 'solid-bg': 'var(--color-surface-raised)', + 'solid-fg': 'var(--color-content-primary)', + 'solid-border': 'var(--color-border-default)', + 'solid-hover-bg': 'color-mix( in srgb, var(--color-surface-overlay) 68%, var(--toggle-palette-track) )', - 'surface-hover-border': + 'solid-hover-border': 'color-mix( in srgb, var(--toggle-palette-border) 45%, var(--color-border-strong) )', - 'surface-on-bg': 'var(--toggle-palette-element)', - 'surface-on-fg': 'var(--toggle-palette-text)', - 'surface-on-border': 'var(--toggle-palette-border)', - 'surface-on-hover-bg': 'var(--toggle-palette-hover)', - 'surface-on-hover-border': 'var(--toggle-palette-border)', + 'solid-on-bg': 'var(--toggle-palette-solid)', + 'solid-on-fg': 'var(--toggle-palette-contrast)', + 'solid-on-border': 'var(--toggle-palette-solid)', + 'solid-on-hover-bg': 'var(--toggle-palette-solid-hover)', + 'solid-on-hover-border': 'var(--toggle-palette-solid-hover)', 'outline-bg': 'transparent', 'outline-fg': 'var(--toggle-palette-text)', 'outline-border': diff --git a/src/uix/eidos/recipe-css-contract.test.ts b/src/uix/eidos/recipe-css-contract.test.ts new file mode 100644 index 000000000..356a6ae41 --- /dev/null +++ b/src/uix/eidos/recipe-css-contract.test.ts @@ -0,0 +1,113 @@ +import { existsSync, readdirSync, readFileSync } from 'node:fs' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' + +import { THEME_BASE_RECIPE_TOKENS } from './lib/recipes/base' + +const COMPONENTS_DIR = 'src/uix/eidos/components' +const CSS_CUSTOM_PROPERTY = /--([a-z][a-z0-9]*-[a-z0-9-]+)/g +const COMPONENT_SOURCE_FILE = /\.(css|svelte|ts)$/ +const RECIPE_TOKENS = THEME_BASE_RECIPE_TOKENS as Readonly< + Record>> +> + +function stripCssComments(css: string): string { + return css.replace(/\/\*[\s\S]*?\*\//g, '') +} + +function recipeTokenName(component: string, key: string): string { + return `--${component}-${key}` +} + +function readComponentCssFiles(): readonly { component: string; css: string }[] { + return readdirSync(COMPONENTS_DIR, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => { + const file = join(COMPONENTS_DIR, entry.name, `${entry.name}.css`) + return { component: entry.name, file } + }) + .filter(({ file }) => existsSync(file)) + .map(({ component, file }) => ({ + component, + css: stripCssComments(readFileSync(file, 'utf8')) + })) +} + +function readTextFilesRecursive(dir: string): readonly string[] { + return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const path = join(dir, entry.name) + + if (entry.isDirectory()) return readTextFilesRecursive(path) + if (!COMPONENT_SOURCE_FILE.test(entry.name)) return [] + + return [readFileSync(path, 'utf8')] + }) +} + +function readComponentSource(component: string): string { + const dir = join(COMPONENTS_DIR, component) + return existsSync(dir) ? readTextFilesRecursive(dir).join('\n') : '' +} + +function readRecipeValueReferences(): string { + return Object.values(RECIPE_TOKENS) + .flatMap((tokens) => Object.values(tokens)) + .join('\n') +} + +function collectOwnPublicVariables(component: string, css: string): readonly string[] { + const ownPrefix = `--${component}-` + return [ + ...new Set( + [...css.matchAll(CSS_CUSTOM_PROPERTY)] + .map((match) => `--${match[1]}`) + .filter((name) => name.startsWith(ownPrefix)) + ) + ].sort() +} + +describe('Eidos recipe CSS contract', () => { + it('declares every public component variable consumed by component CSS', () => { + const missing: string[] = [] + + for (const { component, css } of readComponentCssFiles()) { + const declared = new Set( + Object.keys(RECIPE_TOKENS[component] ?? {}).map((key) => + recipeTokenName(component, key) + ) + ) + + for (const name of collectOwnPublicVariables(component, css)) { + if (!declared.has(name)) missing.push(`${component}: ${name}`) + } + } + + expect(missing).toEqual([]) + }) + + it('does not declare recipes for missing component CSS files', () => { + const cssComponents = new Set( + readComponentCssFiles().map(({ component }) => component) + ) + const extra = Object.keys(THEME_BASE_RECIPE_TOKENS) + .filter((component) => !cssComponents.has(component)) + + expect(extra).toEqual([]) + }) + + it('does not leave declared public recipe variables orphaned', () => { + const recipeValueReferences = readRecipeValueReferences() + const orphaned: string[] = [] + + for (const [component, tokens] of Object.entries(RECIPE_TOKENS)) { + const source = `${readComponentSource(component)}\n${recipeValueReferences}` + + for (const key of Object.keys(tokens)) { + const name = recipeTokenName(component, key) + if (!source.includes(name)) orphaned.push(`${component}: ${name}`) + } + } + + expect(orphaned).toEqual([]) + }) +})