diff --git a/docs/audit/theming/file-upload.md b/docs/audit/theming/file-upload.md index 185aac91e..616b95b12 100644 --- a/docs/audit/theming/file-upload.md +++ b/docs/audit/theming/file-upload.md @@ -5,18 +5,16 @@ > Vista de conjunto: [README](./README.md) · método y protocolo: > [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7. -- **Medido**: 2026-08-23 · **Alcance**: **76%** — 45 de 59 knobs por token público -- **Knobs de apariencia**: 61 — público 45 · privado 9 · global 1 · literal 4 · sistema 2 · excepción 0 _(los dos últimos, fuera del ratio)_ -- **Contrato hoy** (`lib/recipes/base.ts`): 98 pública(s) — `gap-sm`, `gap-md`, `gap-lg`, `padding-sm`, `padding-md`, `padding-lg`, `control-height-sm`, `control-height-md`, `control-height-lg`, `font-size-sm`, `font-size-md`, `font-size-lg`, `preview-size-sm`, `preview-size-md`, `preview-size-lg`, `font-family`, `line-height`, `fg`, `label-fg`, `label-font-weight`, `dropzone-gap`, `dropzone-min-height`, `dropzone-border-width`, `dropzone-border`, `dropzone-radius`, `dropzone-bg`, `dropzone-fg`, `button-gap`, `button-padding-inline`, `button-border-width`, `button-border`, `hover-button-border`, `button-radius`, `button-bg`, `button-fg`, `hover-button-fg`, `button-solid-fg`, `list-gap`, `item-gap`, `item-padding`, `item-border-width`, `item-border`, `item-radius`, `item-bg`, `item-shadow`, `preview-radius`, `preview-bg`, `preview-fg`, `item-name-fg`, `item-name-font-weight`, `item-size-fg`, `item-size-font-size`, `progress-height`, `progress-radius`, `progress-track-bg`, `transition-duration`, `transition-ease`, `disabled-opacity`, `primary-solid`, `primary-solid-hover`, `primary-text`, `secondary-solid`, `secondary-solid-hover`, `secondary-text`, `neutral-solid`, `neutral-solid-hover`, `neutral-text`, `affirm-solid`, `affirm-solid-hover`, `affirm-text`, `fulfill-solid`, `fulfill-solid-hover`, `fulfill-track`, `fulfill-border`, `fulfill-text`, `risk-solid`, `risk-solid-hover`, `risk-text`, `threat-solid`, `threat-solid-hover`, `threat-text`, `loss-solid`, `loss-solid-hover`, `loss-track`, `loss-border`, `loss-text`, `primary-track`, `primary-border`, `secondary-track`, `secondary-border`, `neutral-track`, `neutral-border`, `affirm-track`, `affirm-border`, `risk-track`, `risk-border`, `threat-track`, `threat-border` · 5 privada(s) forward — `_palette-solid`, `_palette-solid-hover`, `_palette-track`, `_palette-border`, `_palette-text` +- **Medido**: 2026-08-23 · **Alcance**: **83%** — 45 de 54 knobs por token público +- **Knobs de apariencia**: 56 — público 45 · privado 9 · global 0 · literal 0 · sistema 2 · excepción 4 _(los dos últimos, fuera del ratio)_ +- **Contrato hoy** (`lib/recipes/base.ts`): 98 pública(s) — `gap-sm`, `gap-md`, `gap-lg`, `padding-sm`, `padding-md`, `padding-lg`, `control-height-sm`, `control-height-md`, `control-height-lg`, `font-size-sm`, `font-size-md`, `font-size-lg`, `preview-size-sm`, `preview-size-md`, `preview-size-lg`, `font-family`, `line-height`, `fg`, `label-fg`, `label-font-weight`, `dropzone-gap`, `dropzone-min-height`, `dropzone-border-width`, `dropzone-border`, `dropzone-radius`, `dropzone-bg`, `dropzone-fg`, `button-gap`, `button-padding-inline`, `button-border-width`, `button-border`, `hover-button-border`, `button-radius`, `button-fg`, `hover-button-fg`, `button-solid-fg`, `list-gap`, `item-gap`, `item-padding`, `item-border-width`, `item-border`, `item-radius`, `item-bg`, `item-shadow`, `preview-radius`, `preview-bg`, `preview-fg`, `item-name-fg`, `item-name-font-weight`, `item-size-fg`, `item-size-font-size`, `progress-height`, `progress-radius`, `progress-track-bg`, `transition-duration`, `transition-ease`, `disabled-opacity`, `invalid-dropzone-border`, `primary-solid`, `primary-solid-hover`, `primary-text`, `secondary-solid`, `secondary-solid-hover`, `secondary-text`, `neutral-solid`, `neutral-solid-hover`, `neutral-text`, `affirm-solid`, `affirm-solid-hover`, `affirm-text`, `fulfill-solid`, `fulfill-solid-hover`, `fulfill-track`, `fulfill-border`, `fulfill-text`, `risk-solid`, `risk-solid-hover`, `risk-text`, `threat-solid`, `threat-solid-hover`, `threat-text`, `loss-solid`, `loss-solid-hover`, `loss-track`, `loss-border`, `loss-text`, `primary-track`, `primary-border`, `secondary-track`, `secondary-border`, `neutral-track`, `neutral-border`, `affirm-track`, `affirm-border`, `risk-track`, `risk-border`, `threat-track`, `threat-border` · 5 privada(s) forward — `_palette-solid`, `_palette-solid-hover`, `_palette-track`, `_palette-border`, `_palette-text` - **Eje `size`**: sí · **ficheros**: `file-upload.css` ## 1. Knobs fuera de alcance -### 1.1 Directo a primitivo global (1) +### 1.1 Directo a primitivo global (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `file-upload.css:208` | `[data-file-upload][data-invalid] [data-file-upload-dropzone]` | `border-color` | `var(--color-risk-border)` | +_Ninguno._ ### 1.2 A través de un privado (9) @@ -25,30 +23,30 @@ | 1 | `file-upload.css:69` | `[data-file-upload-dropzone]:hover:not([data-disabled]), [data-file-upload-dropzone][data-dragging]` | `border-color` | `var(--_file-upload-palette-border)` | | 2 | `file-upload.css:70` | `[data-file-upload-dropzone]:hover:not([data-disabled]), [data-file-upload-dropzone][data-dragging]` | `background` | `var(--_file-upload-palette-track)` | | 3 | `file-upload.css:71` | `[data-file-upload-dropzone]:hover:not([data-disabled]), [data-file-upload-dropzone][data-dragging]` | `color` | `var(--_file-upload-palette-text)` | -| 4 | `file-upload.css:108` | `[data-file-upload-trigger]` | `border-color` | `var(--_file-upload-palette-solid)` | -| 5 | `file-upload.css:109` | `[data-file-upload-trigger]` | `background` | `var(--_file-upload-palette-solid)` | -| 6 | `file-upload.css:114` | `[data-file-upload-trigger]:hover:not([data-disabled])` | `border-color` | `var(--_file-upload-palette-solid-hover)` | -| 7 | `file-upload.css:115` | `[data-file-upload-trigger]:hover:not([data-disabled])` | `background` | `var(--_file-upload-palette-solid-hover)` | -| 8 | `file-upload.css:195` | `[data-file-upload-item-progress]::before` | `inline-size` | `calc(var(--_file-upload-progress-value, 0) * 1%)` | -| 9 | `file-upload.css:197` | `[data-file-upload-item-progress]::before` | `background` | `var(--_file-upload-palette-solid)` | +| 4 | `file-upload.css:107` | `[data-file-upload-trigger]` | `border-color` | `var(--_file-upload-palette-solid)` | +| 5 | `file-upload.css:108` | `[data-file-upload-trigger]` | `background` | `var(--_file-upload-palette-solid)` | +| 6 | `file-upload.css:113` | `[data-file-upload-trigger]:hover:not([data-disabled])` | `border-color` | `var(--_file-upload-palette-solid-hover)` | +| 7 | `file-upload.css:114` | `[data-file-upload-trigger]:hover:not([data-disabled])` | `background` | `var(--_file-upload-palette-solid-hover)` | +| 8 | `file-upload.css:194` | `[data-file-upload-item-progress]::before` | `inline-size` | `calc(var(--_file-upload-progress-value, 0) * 1%)` | +| 9 | `file-upload.css:196` | `[data-file-upload-item-progress]::before` | `background` | `var(--_file-upload-palette-solid)` | -### 1.3 Literales (4) +### 1.3 Literales (0) -| # | fichero:línea | selector | propiedad | valor | -| ---: | --- | --- | --- | --- | -| 1 | `file-upload.css:10` | `[data-file-upload]` | `inline-size` | `100%` | -| 2 | `file-upload.css:162` | `[data-file-upload-item-preview] :where(img, video)` | `inline-size` | `100%` | -| 3 | `file-upload.css:163` | `[data-file-upload-item-preview] :where(img, video)` | `block-size` | `100%` | -| 4 | `file-upload.css:184` | `[data-file-upload-item-progress]` | `inline-size` | `100%` | +_Ninguno._ -### 1.4 Excepciones firmadas (0) — fuera del ratio +### 1.4 Excepciones firmadas (4) — fuera del ratio Literales que llevan su anotación `/* literal: */` en la propia declaración: la válvula de recipe-contract §3, la misma que honra `component-audit`. **Una desviación firmada no es deuda** — se listan para que la razón se lea, no para acuñarlas. -_Ninguno._ +| # | fichero:línea | selector | propiedad | valor | +| ---: | --- | --- | --- | --- | +| 1 | `file-upload.css:10` | `[data-file-upload]` | `inline-size` | `100%` | +| 2 | `file-upload.css:161` | `[data-file-upload-item-preview] :where(img, video)` | `inline-size` | `100%` | +| 3 | `file-upload.css:162` | `[data-file-upload-item-preview] :where(img, video)` | `block-size` | `100%` | +| 4 | `file-upload.css:183` | `[data-file-upload-item-progress]` | `inline-size` | `100%` | ## 2. Sistema transversal (2) — informativo, fuera del ratio Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). @@ -56,7 +54,7 @@ Un tema los alcanza **a nivel de sistema**, por diseño (recipe-contract §2). | # | fichero:línea | selector | propiedad | valor | | ---: | --- | --- | --- | --- | | 1 | `file-upload.css:78` | `[data-file-upload-dropzone]:focus-visible, [data-file-upload-trigger]:focus-visible, [data-file-upload-item-remove]:focus-visible, [data-file-upload-clear-trigger]:focus-visible` | `outline` | `var(--focus-ring-width) solid var(--focus-ring-color)` | -| 2 | `file-upload.css:126` | `[data-file-upload-item-remove]:hover:not([data-disabled]), [data-file-upload-clear-trigger]:hover:not([data-disabled])` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` | +| 2 | `file-upload.css:125` | `[data-file-upload-item-remove]:hover:not([data-disabled]), [data-file-upload-clear-trigger]:hover:not([data-disabled])` | `background-image` | `linear-gradient(var(--state-hover), var(--state-hover))` | ## 3. Privados de la receta — ¿de dónde sale su valor? @@ -74,7 +72,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl - **Tiene eje `size`**: los tokens dimensionales van por talla (`{part}-{eje}-{k}`) apuntando al bundle `--size-{k}-*`, nunca al primitivo crudo (theming §5; el guard `recipe-css-contract` prohíbe el primitivo). -### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1) +### 4.1 Tokens a declarar en `lib/recipes/base.ts` (0) Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla @@ -84,7 +82,6 @@ no distingue lo que debería — se marca `⚠`. | token (`--file-upload-…`) | scope TSC | valor propuesto | usos | | --- | --- | --- | ---: | -| `invalid-dropzone-border` | `root` | `var(--color-risk-border)` | 1 | ### 4.2 Sin nombre mecánico (13) @@ -103,6 +100,61 @@ no distingue lo que debería — se marca `⚠`. -_(pendiente — lo escribe el autor; se conserva al regenerar)_ +**EJECUTADO 2026-08-23 — 76 % → 83 %.** Una clave acuñada, **una retirada con su +declaración muerta**, cuatro literales firmados y cuarenta y cuatro +adjudicaciones medidas (4 propias + 40 por patrón). Diff de computed **0 sobre +11.448 valores en 17 estados** y capturas 2× **idénticas byte a byte**. + +**`invalid-dropzone-border`** era el único knob que iba a un primitivo global a +pelo (`--color-risk-border`). Lleva la PARTE en el nombre —y no el +`invalid-border` que el catálogo repite seis veces— porque aquí hay TRES partes +con borde (dropzone, botón, ítem) y el nombre desnudo no diría cuál tiñe. Su +estado no lo alcanza esta ruta: `isInvalid` sale de un `Field` envolvente y la +demo no monta ninguno, así que se mide forzando el atributo +(`oklch(0.8059 0.1123 59.96)` → `rgb(1,2,3)`). + +⚠ **`button-bg` NO PINTABA, y se retira con su declaración.** La regla +compartida de los tres botones declaraba `background: var(--file-upload-button-bg)` +y los tres la pisan después: el trigger con el sólido de la paleta (línea 108), +`ItemRemove` y `ClearTrigger` con `transparent` (línea 119). Medido sobre los +tres nodos, uno a uno: el sentinel no mueve NADA en ninguno +(`oklch(0.5556 …)` y `rgba(0,0,0,0)` intactos). Es la clase «un token que no +mueve nada es un token que miente» de F2-A, y con UNA clave la respuesta es +retirar, no adjudicar: retirarla da **diff 0**, que es la prueba de que estaba +muerta. La clave sale del contrato con ella (un público sin consumidor es un +huérfano). + +**Cuatro literales firmados** en vez de acuñados, con su anotación en la propia +declaración: el `100%` del campo (ocupa el ancho que le dan), los dos de la +miniatura (`img`/`video` LLENAN la caja en la que se recortan) y el de la barra +de progreso (ocupa la fila que le da el grid). Identidad, no knob. + +⚠⚠ **CUARENTA claves de tono no pintan** — 8 tonos × 5 ranuras +(`solid`, `solid-hover`, `track`, `border`, `text`), 40 de sus 98. Es la cascada +de paleta: el forward emite AL FINAL un bloque genérico +`[data-file-upload][data-color]` que resuelve desde el `--palette-*` global, +misma especificidad (0,2,0), gana el último. Medido para los ocho tonos sobre +las dos superficies que las consumen —el trigger (`solid`) y el dropzone en +hover (`track`)—: ninguna se mueve, mientras `--palette-solid` sobre el nodo +repinta. Su `host` es `primary` y la demo pasa `color` explícito, así que el +fallback tampoco actúa. Adjudicadas por PATRÓN; incidencia de fondo en +`next-features.md` §13. + +**Los nueve privados se quedan, y ninguno es deuda de valor**: ocho son el +puente de paleta THM-2 (`--_file-upload-palette-*` en el hover/arrastre del +dropzone, en el trigger y en el relleno de la barra) —un público encima dejaría +que un tema fijara el tono y matara el `color=` de cada instancia— y el noveno +es **el canal de valor**, `--_file-upload-progress-value`, que +`file-upload-item-progress.svelte` escribe INLINE por fila. Lo que el componente +escribe inline no es tema. **El techo honesto de este componente no es el +100 %.** + +**Medición**: sonda estándar 0 diffs (4.608 valores · 8 estados · 19 nodos, la +demo arranca con dos ficheros sembrados, así que monta la superficie entera) más +un barrido de estados que la ruta no arranca —3 variantes × 3 tallas, `invalid`, +`disabled` y `dragging`— con el CSS de HEAD **inyectado en la página** como +control (gana por orden a igual especificidad): 0 diffs sobre 6.840 valores. El +instrumento pasó su muta-prueba: alterando el CSS de control acusa 19 diffs, y 1 +si sólo se altera el borde inválido. diff --git a/docs/process/PLAN-theming.md b/docs/process/PLAN-theming.md index 09bd5fc37..4953821fc 100644 --- a/docs/process/PLAN-theming.md +++ b/docs/process/PLAN-theming.md @@ -537,6 +537,26 @@ commits de corrección. animaciones (ningún commit toca `animation`); contrato sin duplicados ni huérfanos. +- 2026-08-23 — **`file-upload` 76 % → 83 %** (COMMIT), 1 clave acuñada y **1 + RETIRADA**. `invalid-dropzone-border` (con la PARTE en el nombre: tres partes + con borde) era el único knob a un primitivo a pelo. **`button-bg` no pintaba**: + la regla compartida de los tres botones la declaraba y los tres la pisan + después (el trigger con el sólido de la paleta, `ItemRemove` y `ClearTrigger` + con `transparent`) — medido sobre los tres nodos, cero movimiento, así que se + retira la declaración Y la clave, y el diff 0 lo prueba. **Cuatro literales de + identidad firmados** (el ancho del campo, los dos de la miniatura, el de la + barra). **CUARENTA claves de tono** (8 × 5 ranuras) adjudicadas por patrón: la + cascada de paleta, medida sobre el trigger y el dropzone en hover para los + ocho tonos. Los 9 privados son 8 puentes THM-2 + **el canal de valor** + (`--_file-upload-progress-value`, inline por fila), así que **su techo honesto + no es el 100 %**. R-5.4 54/98, exit 0. Diff 0 sobre **11.448 valores en 17 + estados** y capturas 2× idénticas byte a byte. ⚠ **Truco de medición nuevo**: + el barrido de estados que la demo no arranca (variante × talla × invalid × + disabled × dragging) reconstruye el «antes» **inyectando el CSS de HEAD en la + página** —gana por orden a igual especificidad—, sin tocar el árbol que otras + tres sesiones están usando; con su muta-prueba (19 diffs al alterar el control, + 1 al alterar sólo el borde inválido). + - 2026-08-23 — **`switch` 76 % → 81 %** (COMMIT), 1 clave. `invalid-track-border` era el ÚNICO knob que iba a un primitivo a pelo (`--color-threat-element`). **El nombre NO es el `invalid-border` del catálogo** (6 recetas lo usan): diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts index 996dbf2aa..272dca34e 100644 --- a/scripts/theming-sentinel-exceptions.ts +++ b/scripts/theming-sentinel-exceptions.ts @@ -1006,6 +1006,20 @@ export const SENTINEL_EXCEPTIONS: Record> = { 'rim-glow-blur': 'same look, same route -> reaches (blur(4px) -> blur(13px))', 'rim-glow-opacity': 'same look, and the rim is under an ANIMATION: with only the transitions frozen the read returns the animated value (0.482956) and the token looks dead. With animation frozen too it computes 0.45 and follows -> reaches (0.45 -> 0.137)' }, + 'file-upload': { + // Measured one by one 2026-08-23 (`__fu-adj`). The demo mounts the WHOLE + // surface (two seeded files, so list, preview, name, size, progress, remove + // and clear all render): what is left is state the route cannot reach and + // the freeze the guard does on purpose. + 'transition-duration': + 'the guard freezes transitions and this token IS the transition; measured unfrozen on the dropzone -> reaches (0.12s -> 11.5s on its three properties)', + 'transition-ease': + 'same instrument limit; measured unfrozen -> reaches (cubic-bezier(0.4, 0, 0.2, 1) -> steps(7))', + 'disabled-opacity': + 'only under [data-disabled]; forced on the provider -> reaches (0.4 -> 0.123)', + 'invalid-dropzone-border': + 'only under [data-invalid], and this component takes invalid from an enclosing Field, which the demo route does not mount; forced -> reaches (oklch(0.8059 0.1123 59.96) -> rgb(1,2,3))' + }, switch: { // Measured one by one 2026-08-23 (`__sw-adj`). The demo stage boots // UNCHECKED, VALID and ENABLED and the switch has no trigger to open, so @@ -1097,6 +1111,14 @@ export const SENTINEL_PATTERN_EXCEPTIONS: Array<{ pattern: /^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(track|solid|contrast)$/, reason: PALETTE_SUPERSEDED }, + { + component: 'file-upload', + pattern: + /^(primary|secondary|neutral|affirm|fulfill|risk|threat|loss)-(solid|solid-hover|track|border|text)$/, + reason: + PALETTE_SUPERSEDED + + ' — the eight tones x five slots, 40 of its 97 keys. Measured 2026-08-23 on the trigger (solid) and on the hovered dropzone (track) for the eight tones: not one moves, while --palette-solid on the node repaints (oklch(0.6908 …) -> rgb(4,5,6)). Its host default is `primary`, and the demo passes color explicitly, so the fallback never applies either' + }, { component: 'switch', pattern: /^(primary|secondary|neutral|affirm|risk|threat)-(solid|solid-hover)$/, diff --git a/src/uix/eidos/components/file-upload/README.md b/src/uix/eidos/components/file-upload/README.md index 0d46d26c4..345f3fe73 100644 --- a/src/uix/eidos/components/file-upload/README.md +++ b/src/uix/eidos/components/file-upload/README.md @@ -113,6 +113,44 @@ References: ``` +## Talla y tema + +**98 claves públicas** en `lib/recipes/base.ts` + cinco forwards de paleta THM-2 +(`_palette-{solid,solid-hover,track,border,text}`). El eje `size` (sm/md/lg) +lleva cinco coordenadas por paso —`gap`, `padding`, `control-height`, +`font-size` y `preview-size`—, que la receta resuelve por privado. La pestaña +**Tokens** de su demo las resuelve en vivo. + +Lo que cambió el **2026-08-23** (76 % → 83 %): + +- **`invalid-dropzone-border`** (nueva): era el único knob que iba a un + primitivo global a pelo (`--color-risk-border`). Lleva la PARTE en el nombre + porque aquí hay tres partes con borde —dropzone, botón e ítem—, así que el + `invalid-border` desnudo del catálogo no diría cuál tiñe. +- **`button-bg` RETIRADA, con su declaración**: la regla compartida de los tres + botones pintaba `background: var(--file-upload-button-bg)` y los tres la + pisan después — el trigger con el sólido de la paleta, `ItemRemove` y + `ClearTrigger` con `transparent`. Medida sobre los tres nodos: no movía nada + en ninguno. Retirarla da diff 0, que es la prueba de que estaba muerta. +- **Cuatro literales FIRMADOS** (`/* literal: … */`, la válvula de + recipe-contract §3): los tres `100%` de identidad —el campo ocupa el ancho que + le dan, la miniatura llena su caja, la barra ocupa su fila— y el `block-size` + de la miniatura. + +⚠ **Las CUARENTA claves de tono no pintan** (8 tonos × `solid`, `solid-hover`, +`track`, `border`, `text`). El forward de paleta emite AL FINAL un bloque +genérico `[data-file-upload][data-color]` que resuelve desde el `--palette-*` +global: misma especificidad (0,2,0), gana el último. Medido sobre el trigger y +sobre el dropzone en hover para los ocho tonos. Es la incidencia de 419 claves +en 49 recetas de `next-features.md` §13 — no se arregla aquí. + +**Lo que queda fuera por doctrina**: las nueve lecturas de privado son ocho +puentes THM-2 (`--_file-upload-palette-*` en el hover/arrastre del dropzone, en +el trigger y en el relleno de la barra) y **el canal de valor** +`--_file-upload-progress-value`, que el envoltorio escribe INLINE por fila. Ni +uno ni otro se acuñan: un público sobre el puente dejaría que un tema fijara el +tono y matara el `color=` de cada instancia, y el canal de valor no es tema. + ## Comparativa | Capacidad | Bits UI | Ark UI | React Aria | shadcn-svelte | UIX | diff --git a/src/uix/eidos/components/file-upload/file-upload.css b/src/uix/eidos/components/file-upload/file-upload.css index 11bb2dc98..539e1f385 100644 --- a/src/uix/eidos/components/file-upload/file-upload.css +++ b/src/uix/eidos/components/file-upload/file-upload.css @@ -7,7 +7,7 @@ display: grid; gap: var(--_file-upload-gap); - inline-size: 100%; + inline-size: 100%; /* literal: identity — the field IS its container's width */ color: var(--file-upload-fg); font-family: var(--file-upload-font-family); font-size: var(--_file-upload-font-size); @@ -91,7 +91,6 @@ padding-inline: var(--file-upload-button-padding-inline); border: var(--file-upload-button-border-width) solid var(--file-upload-button-border); border-radius: var(--file-upload-button-radius); - background: var(--file-upload-button-bg); color: var(--file-upload-button-fg); font: inherit; text-decoration: none; @@ -159,8 +158,8 @@ } [data-file-upload-item-preview] :where(img, video) { - inline-size: 100%; - block-size: 100%; + inline-size: 100%; /* literal: identity — the media FILLS the preview box it is cropped into */ + block-size: 100%; /* literal: identity — same box, the other axis */ object-fit: cover; } @@ -181,7 +180,7 @@ position: relative; overflow: hidden; grid-column: 1 / -1; - inline-size: 100%; + inline-size: 100%; /* literal: identity — the bar spans the row it is given */ block-size: var(--file-upload-progress-height); border-radius: var(--file-upload-progress-radius); background: var(--file-upload-progress-track-bg); @@ -205,7 +204,7 @@ } [data-file-upload][data-invalid] [data-file-upload-dropzone] { - border-color: var(--color-risk-border); + border-color: var(--file-upload-invalid-dropzone-border); } @media (prefers-reduced-motion: reduce) { diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index ed5476b94..d5bc05046 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -2938,7 +2938,6 @@ --file-upload-button-border: var(--color-border-subtle); --file-upload-hover-button-border: var(--color-border-default); --file-upload-button-radius: var(--radius-md); - --file-upload-button-bg: var(--color-surface-raised); --file-upload-button-fg: var(--color-content-secondary); --file-upload-hover-button-fg: var(--color-content-primary); --file-upload-button-solid-fg: var(--color-content-on-solid); @@ -2963,6 +2962,7 @@ --file-upload-transition-duration: var(--duration-fast); --file-upload-transition-ease: var(--ease-default); --file-upload-disabled-opacity: var(--opacity-disabled); + --file-upload-invalid-dropzone-border: var(--color-risk-border); --file-upload-primary-solid: var(--color-primary-solid); --file-upload-primary-solid-hover: var(--color-primary-solid-hover); --file-upload-primary-text: var(--color-primary-text); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index d87c4c9aa..a38d7ca68 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -4470,7 +4470,6 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ 'button-border': 'var(--color-border-subtle)', 'hover-button-border': 'var(--color-border-default)', 'button-radius': 'var(--radius-md)', - 'button-bg': 'var(--color-surface-raised)', 'button-fg': 'var(--color-content-secondary)', 'hover-button-fg': 'var(--color-content-primary)', 'button-solid-fg': 'var(--color-content-on-solid)', @@ -4495,6 +4494,10 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({ 'transition-duration': 'var(--duration-fast)', 'transition-ease': 'var(--ease-default)', 'disabled-opacity': 'var(--opacity-disabled)', + // The Field-aware invalid tint, on the DROPZONE: this recipe has three + // bordered parts (dropzone, button, item), so the catalogue's bare + // `invalid-border` would not say which one it tints. + 'invalid-dropzone-border': 'var(--color-risk-border)', 'primary-solid': 'var(--color-primary-solid)', 'primary-solid-hover': 'var(--color-primary-solid-hover)', 'primary-text': 'var(--color-primary-text)', diff --git a/web/routes/uix/components/file-upload/+page.svelte b/web/routes/uix/components/file-upload/+page.svelte index c54dbfbbb..b57a2351e 100644 --- a/web/routes/uix/components/file-upload/+page.svelte +++ b/web/routes/uix/components/file-upload/+page.svelte @@ -12,8 +12,9 @@ import { getActiveUix } from '$active-uix'; import { page } from '$app/state'; import { colorValue, PALETTE_SCALES } from '../../lib/harness.svelte'; + import TokensPanel from '../../lib/TokensPanel.svelte'; - type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; + type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'tokens' | 'a11y'; const uix = getActiveUix(); const params = page.url.searchParams; @@ -315,6 +316,9 @@ {events.length} + @@ -587,6 +591,10 @@ {/if} + {#if tab === 'tokens'} + + {/if} + {#if tab === 'recipe'}

Eidos recipe