From 60a30c2e60ff0e87ba420af79ec0154f9dd5700e Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 18 Jul 2026 21:33:48 +0200 Subject: [PATCH] =?UTF-8?q?uix(color):=20abre=20`color`=20en=20skeleton=20?= =?UTF-8?q?=C2=B7=20spinner=20=C2=B7=20textarea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tres standalones al forward de paleta compartido (`resolveComponentColor` + recipe `_palette-*`): - skeleton: tinta el wash (slot track, 50% alpha) + el highlight del shimmer. Colored-only: el default `'neutral'` NO estampa data-color y conserva su mezcla base cross-mode (oklab + 16% solid). Delta declarado: affirm/fulfill/ risk/threat/loss ganan highlight tintado en el shimmer (antes solo primary/ secondary lo definian; la regla generica uniformiza la cobertura). - spinner: patron C puro — la base consume `--_spinner-palette-{text,track}` (host = primary, behavior-preserving); fuera los 7 bloques per-rol. `'inherit'` se conserva como union aditiva con su regla literal (currentColor); no puede viajar por el seed custom (trampa `--x: inherit`). - textarea: tinta el borde de focus (slot solid). Colored-only: el default `'primary'` no estampa y conserva `--focus-ring-color` (que NO es primary-solid: es un mix al 52% — estampar habria cambiado el default). Verificado en Chrome (probe getComputedStyle): default intacto en los tres; risk/teal/amber/jade = token compartido exacto; custom hex → color-mix del seed. check: 0 errores en los componentes tocados; contract test 26/28 (2 pre-existentes de proof-of-human). Co-Authored-By: Claude Fable 5 --- .../eidos/components/skeleton/skeleton.css | 30 +++++----------- .../eidos/components/skeleton/skeleton.svelte | 17 +++++++-- src/uix/eidos/components/skeleton/types.ts | 9 +++-- src/uix/eidos/components/spinner/spinner.css | 36 +++---------------- .../eidos/components/spinner/spinner.svelte | 19 +++++++++- src/uix/eidos/components/spinner/types.ts | 10 +++--- .../eidos/components/textarea/textarea.css | 30 ++++------------ .../eidos/components/textarea/textarea.svelte | 13 ++++++- src/uix/eidos/components/textarea/types.ts | 10 ++++-- src/uix/eidos/generated/base.css | 26 ++++++++++++++ src/uix/eidos/lib/recipes/base.ts | 19 ++++++++++ 11 files changed, 128 insertions(+), 91 deletions(-) diff --git a/src/uix/eidos/components/skeleton/skeleton.css b/src/uix/eidos/components/skeleton/skeleton.css index 284be5867..2ad6fd0f9 100644 --- a/src/uix/eidos/components/skeleton/skeleton.css +++ b/src/uix/eidos/components/skeleton/skeleton.css @@ -76,28 +76,14 @@ /* ── Color accents ─────────────────────────────────────────────────── */ -[data-skeleton][data-color='primary'] { - --_skeleton-bg: color-mix(in srgb, var(--color-primary-track) 50%, transparent); - --_skeleton-highlight: color-mix(in srgb, var(--color-primary-track) 90%, white 8%); -} -[data-skeleton][data-color='secondary'] { - --_skeleton-bg: color-mix(in srgb, var(--color-secondary-track) 50%, transparent); - --_skeleton-highlight: color-mix(in srgb, var(--color-secondary-track) 90%, white 8%); -} -[data-skeleton][data-color='affirm'] { - --_skeleton-bg: color-mix(in srgb, var(--color-affirm-track) 50%, transparent); -} -[data-skeleton][data-color='fulfill'] { - --_skeleton-bg: color-mix(in srgb, var(--color-fulfill-track) 50%, transparent); -} -[data-skeleton][data-color='risk'] { - --_skeleton-bg: color-mix(in srgb, var(--color-risk-track) 50%, transparent); -} -[data-skeleton][data-color='threat'] { - --_skeleton-bg: color-mix(in srgb, var(--color-threat-track) 50%, transparent); -} -[data-skeleton][data-color='loss'] { - --_skeleton-bg: color-mix(in srgb, var(--color-loss-track) 50%, transparent); +/* Colour comes from the shared palette forward (`--_skeleton-palette-track`): + any role / 33 scale / custom value tints the wash + shimmer highlight. + `'neutral'` (the default) never stamps `data-color`, so the base cross-mode + grey mix above stays untouched. */ +[data-skeleton][data-color], +[data-skeleton][data-color-custom] { + --_skeleton-bg: color-mix(in srgb, var(--_skeleton-palette-track) 50%, transparent); + --_skeleton-highlight: color-mix(in srgb, var(--_skeleton-palette-track) 90%, white 8%); } /* ── Animation (shimmer wave) ──────────────────────────────────────── */ diff --git a/src/uix/eidos/components/skeleton/skeleton.svelte b/src/uix/eidos/components/skeleton/skeleton.svelte index 49cea628e..988d3efd2 100644 --- a/src/uix/eidos/components/skeleton/skeleton.svelte +++ b/src/uix/eidos/components/skeleton/skeleton.svelte @@ -13,6 +13,8 @@ */ import './skeleton.css'; import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import { SKELETON_LANGS } from './langs'; import type { SkeletonProps } from './types'; @@ -23,6 +25,7 @@ lines = 1, animated = true, 'aria-label': ariaLabel, + style, children, ...rest }: SkeletonProps = $props(); @@ -33,6 +36,12 @@ const resolvedSize = $derived(eidos.resolve(size, 'md')); const resolvedLabel = $derived(ariaLabel ?? eidos.langs.ts(SKELETON_LANGS.LABEL)); + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed. `'neutral'` (the default) + // stamps nothing so the base grey wash keeps its cross-mode mix. + const colorAttrs = $derived(resolveComponentColor(color === 'neutral' ? undefined : color)); + const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle)); + // Stagger widths for multi-line text skeletons so the block reads as // a paragraph rather than a solid rectangle. Last line always shorter. const lineWidths = $derived.by(() => { @@ -51,11 +60,13 @@ data-skeleton="" data-shape="text" data-size={resolvedSize} - data-color={color} + data-color={colorAttrs.dataColor} + data-color-custom={colorAttrs.isCustom ? '' : undefined} data-animated={animated ? '' : undefined} role="status" aria-busy="true" aria-label={resolvedLabel} + style={mergedStyle} {...rest} > {#each lineWidths as w, i (i)} @@ -67,11 +78,13 @@ data-skeleton="" data-shape={resolvedShape} data-size={resolvedSize} - data-color={color} + data-color={colorAttrs.dataColor} + data-color-custom={colorAttrs.isCustom ? '' : undefined} data-animated={animated ? '' : undefined} role="status" aria-busy="true" aria-label={resolvedLabel} + style={mergedStyle} {...rest} > {@render children?.()} diff --git a/src/uix/eidos/components/skeleton/types.ts b/src/uix/eidos/components/skeleton/types.ts index dd17735af..e5f2281e9 100644 --- a/src/uix/eidos/components/skeleton/types.ts +++ b/src/uix/eidos/components/skeleton/types.ts @@ -1,10 +1,10 @@ import type { Snippet } from 'svelte'; -import type { ColorRole, ResponsiveProp, Size } from '$uix/eidos/lib/types'; +import type { ComponentColorProp, ResponsiveProp, Size } from '$uix/eidos/lib/types'; import type { BoxProps } from '../box/types'; export type SkeletonShape = 'rect' | 'circle' | 'text'; export type SkeletonSize = Extract; -export type SkeletonColor = ColorRole; +export type SkeletonColor = ComponentColorProp; /** * Props for ``. @@ -19,7 +19,10 @@ export type SkeletonProps = Omit & { shape?: ResponsiveProp; /** Reservation size. @default 'md' (only meaningful when no width/height set) */ size?: ResponsiveProp; - /** Accent palette (placeholder background lightness). @default 'neutral' */ + /** + * Placeholder tint — any role / intent / 33 donor scales / raw CSS color. + * `'neutral'` (the default) keeps the base grey wash. @default 'neutral' + */ color?: SkeletonColor; /** * Number of stacked bars when `shape='text'`. Each bar's width diff --git a/src/uix/eidos/components/spinner/spinner.css b/src/uix/eidos/components/spinner/spinner.css index 9a2e05ae9..ab5f2dfb3 100644 --- a/src/uix/eidos/components/spinner/spinner.css +++ b/src/uix/eidos/components/spinner/spinner.css @@ -13,8 +13,8 @@ --_spinner-size: 1.25rem; --_spinner-thickness: 2px; --_spinner-duration: 0.9s; - --_spinner-color: var(--color-primary-text); - --_spinner-track: color-mix(in srgb, var(--color-primary-track) 60%, transparent); + --_spinner-color: var(--_spinner-palette-text); + --_spinner-track: color-mix(in srgb, var(--_spinner-palette-track) 60%, transparent); --_spinner-gap: var(--space-2); display: inline-flex; @@ -44,35 +44,9 @@ --_spinner-thickness: 4px; } -/* Color tokens — accent + track. */ -[data-spinner][data-color='secondary'] { - --_spinner-color: var(--color-secondary-text); - --_spinner-track: color-mix(in srgb, var(--color-secondary-track) 60%, transparent); -} -[data-spinner][data-color='neutral'] { - --_spinner-color: var(--color-neutral-text); - --_spinner-track: color-mix(in srgb, var(--color-neutral-track) 60%, transparent); -} -[data-spinner][data-color='affirm'] { - --_spinner-color: var(--color-affirm-text); - --_spinner-track: color-mix(in srgb, var(--color-affirm-track) 60%, transparent); -} -[data-spinner][data-color='fulfill'] { - --_spinner-color: var(--color-fulfill-text); - --_spinner-track: color-mix(in srgb, var(--color-fulfill-track) 60%, transparent); -} -[data-spinner][data-color='risk'] { - --_spinner-color: var(--color-risk-text); - --_spinner-track: color-mix(in srgb, var(--color-risk-track) 60%, transparent); -} -[data-spinner][data-color='threat'] { - --_spinner-color: var(--color-threat-text); - --_spinner-track: color-mix(in srgb, var(--color-threat-track) 60%, transparent); -} -[data-spinner][data-color='loss'] { - --_spinner-color: var(--color-loss-text); - --_spinner-track: color-mix(in srgb, var(--color-loss-track) 60%, transparent); -} +/* Colour comes from the shared palette forward (`--_spinner-palette-*`): the + base tokens above consume it directly (host default = primary), so any role / + 33 scale / custom value routes through with no per-role cascade. */ /* `inherit` — take the surrounding `currentColor` (for embedding in a control that owns the colour, e.g. a solid Button whose text is the contrast colour). */ diff --git a/src/uix/eidos/components/spinner/spinner.svelte b/src/uix/eidos/components/spinner/spinner.svelte index 108b8ba57..64003b698 100644 --- a/src/uix/eidos/components/spinner/spinner.svelte +++ b/src/uix/eidos/components/spinner/spinner.svelte @@ -12,6 +12,8 @@ */ import './spinner.css'; import { ActiveEidos } from '$uix/eidos'; + import { composeInlineStyle } from '$uix/eidos/lib/style'; + import { resolveComponentColor } from '$uix/eidos/lib/component-color'; import { SPINNER_LANGS } from './langs'; import type { SpinnerProps } from './types'; @@ -21,6 +23,7 @@ color = 'primary', presentation = false, 'aria-label': ariaLabel, + style, children, ...rest }: SpinnerProps = $props(); @@ -29,17 +32,31 @@ const resolvedSize = $derived(eidos.resolve(size, 'md')); const resolvedLabel = $derived(ariaLabel ?? eidos.langs.ts(SPINNER_LANGS.LABEL)); + + // `color` accepts the full system: canonical → `data-color`, raw value → + // `data-color-custom` + the `--color-custom` seed. `'inherit'` keeps its own + // literal stamp — routing it through the custom seed would make the seed + // itself inherit from the parent, not the ink (the `--x: inherit` trap). + const colorAttrs = $derived.by(() => { + if (color === 'inherit') { + return { dataColor: 'inherit', isCustom: false, customStyle: undefined }; + } + return resolveComponentColor(color); + }); + const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
{#if variant === 'dots'} diff --git a/src/uix/eidos/components/spinner/types.ts b/src/uix/eidos/components/spinner/types.ts index f655f91d0..c6ae1906b 100644 --- a/src/uix/eidos/components/spinner/types.ts +++ b/src/uix/eidos/components/spinner/types.ts @@ -1,9 +1,9 @@ import type { Snippet } from 'svelte'; -import type { ColorRole, ResponsiveProp, Size } from '$uix/eidos/lib/types'; +import type { ComponentColorProp, ResponsiveProp, Size } from '$uix/eidos/lib/types'; import type { BoxProps } from '../box/types'; export type SpinnerSize = Extract; -export type SpinnerColor = ColorRole | 'inherit'; +export type SpinnerColor = ComponentColorProp | 'inherit'; export type SpinnerVariant = 'ring' | 'dots' | 'bars'; /** @@ -20,9 +20,9 @@ export type SpinnerProps = Omit & { /** Spinner size. @default 'md' */ size?: ResponsiveProp; /** - * Accent palette, or `'inherit'` to take the surrounding `currentColor` - * (for embedding inside a control that owns the colour, e.g. Button). - * @default 'primary' + * Accent — any role / intent / 33 donor scales / raw CSS color, or + * `'inherit'` to take the surrounding `currentColor` (for embedding inside + * a control that owns the colour, e.g. Button). @default 'primary' */ color?: SpinnerColor; /** diff --git a/src/uix/eidos/components/textarea/textarea.css b/src/uix/eidos/components/textarea/textarea.css index a2746422d..141ca2ce8 100644 --- a/src/uix/eidos/components/textarea/textarea.css +++ b/src/uix/eidos/components/textarea/textarea.css @@ -52,29 +52,13 @@ --_textarea-font-size: var(--font-size-xl); } -/* Color accents — applied on focus + when invalid. Each role's `-solid` (the - saturated accent, step 9) is the existing token closest to the intended strong - focus border. */ -[data-textarea][data-color='secondary'] { - --_textarea-border-focus: var(--color-secondary-solid); -} -[data-textarea][data-color='neutral'] { - --_textarea-border-focus: var(--color-neutral-solid); -} -[data-textarea][data-color='affirm'] { - --_textarea-border-focus: var(--color-affirm-solid); -} -[data-textarea][data-color='fulfill'] { - --_textarea-border-focus: var(--color-fulfill-solid); -} -[data-textarea][data-color='risk'] { - --_textarea-border-focus: var(--color-risk-solid); -} -[data-textarea][data-color='threat'] { - --_textarea-border-focus: var(--color-threat-solid); -} -[data-textarea][data-color='loss'] { - --_textarea-border-focus: var(--color-loss-solid); +/* Color accent — applied on focus. The solid slot (the saturated accent, + step 9) comes from the shared palette forward (`--_textarea-palette-solid`): + any role / 33 scale / custom value routes through. `'primary'` (the default) + never stamps `data-color`, so the base `--focus-ring-color` stays. */ +[data-textarea][data-color], +[data-textarea][data-color-custom] { + --_textarea-border-focus: var(--_textarea-palette-solid); } /* `resize` is owned by eidos (presentation), driven by the morfo's diff --git a/src/uix/eidos/components/textarea/textarea.svelte b/src/uix/eidos/components/textarea/textarea.svelte index 180f4816f..cebc467d8 100644 --- a/src/uix/eidos/components/textarea/textarea.svelte +++ b/src/uix/eidos/components/textarea/textarea.svelte @@ -5,6 +5,8 @@ * Eidos `