Banner: route palette through recipe forwarders (P1-5)

- Add 40 recipes.banner.{intent}-{slot} forwarders (= var(--color-{intent}-{slot}))
- banner.css reads --banner-{intent}-{slot} instead of foundation --color-{intent}-* directly
- Banner is now retintable per-component via recipes.banner.* like button/badge; keeps data-intent (no API change)
- Regenerated base.css; recipe contract balanced (40 declared = 40 consumed), no visual regression

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
active-uix
dev 4 months ago
parent 218ccfcc5e
commit 3adc93b2d1

@ -65,9 +65,10 @@ definiciones vs referencias). Orden de prioridad para empezar a resolver.
- **Fix**: sniff de color CSS (hex/rgb/hsl/color/var/color-mix/nombres) en rutas de color.
- **✅ Hecho**: regla segura en `validateNonEmptyCssValue` — un valor que empieza por `#` debe ser hex válido (`#rgb/#rgba/#rrggbb/#rrggbbaa`); caza `#ggg` / hex tipografiado **sin** tocar `var()`/`color-mix()`/`rgb()` ni los placeholders de test (sin `#`). + test. **Pendiente**: el identificador-pelado tipo `'not-a-color'` sigue sin cazarse (requeriría allow-list de nombres CSS o reescribir el test de pass-through `'purple-alpha-4'`).
### P1-5 · Banner se salta el sistema de recipes/roles
### ✅ P1-5 · Banner se salta el sistema de recipes/roles
- `components/banner/banner.css:57-112` cablea a mano 8 intents × 5 slots a `var(--color-{intent}-{slot})` y lee `data-intent`. Su recipe (`base.ts:3799-3802`) solo declara gap+radius. Es el único componente de color que **no se puede retintar vía `recipes.*`** y diverge del eje `data-color` del resto.
- **Fix**: migrar Banner a tokens de recipe + cascada TSC `_accent-*`/`palette-*` como button/toggle.
- **✅ Hecho** (versión no-disruptiva): añadidos 40 forwarders `recipes.banner.{intent}-{slot}` (= `var(--color-{intent}-{slot})`) y banner.css lee `--banner-{intent}-{slot}` en vez de `--color-{intent}-*` directo → **Banner ya es retintable vía `recipes.banner.*`** (como button/badge). Verificado: cadena resuelve igual (`--banner-affirm-solid` = `#12a594`), sin regresión visual; contract balanceado (40 declarados = 40 consumidos). **Mantiene `data-intent`** (no cambio de API). **Pendiente futuro**: alinear del todo al patrón button (cascada `palette-*` en el recipe en vez de en CSS) requeriría cambiar el eje a `data-color` o un scope `intent:` en el TSC — decisión separada.
### P1-6 · Cobertura de tests: las dos clases de bug que ya se colaron siguen sin red
- **`contrast`/on-solid: cero asserts** (`grep contrast|on-solid` en los `*.test.ts` = 0). El bug de ilegibilidad estaba en este slot.

@ -1,8 +1,9 @@
/*
* Banner recipe — full-bleed announcement strip. Reads `data-intent`
* (the standard UIX color-role channel) and `data-variant` (soft / solid
* / outline / ghost) to pick the right palette mix from the foundation
* `--color-{intent}-*` tokens. `data-size` controls vertical density.
* / outline / ghost) to pick the right palette mix from the recipe
* `--banner-{intent}-*` palette forwarders (overridable via
* `recipes.banner.{intent}-{slot}`). `data-size` controls vertical density.
*
* Variant cascade:
* soft — `--color-{intent}-track` bg + `--color-{intent}-text` fg
@ -55,60 +56,60 @@
/* ── Intent palette resolution ─────────────────────────────────────── */
[data-banner][data-intent='primary'] {
--_banner-solid: var(--color-primary-solid);
--_banner-solid-contrast: var(--color-primary-contrast);
--_banner-track: var(--color-primary-track);
--_banner-border: var(--color-primary-border);
--_banner-text: var(--color-primary-text);
--_banner-solid: var(--banner-primary-solid);
--_banner-solid-contrast: var(--banner-primary-contrast);
--_banner-track: var(--banner-primary-track);
--_banner-border: var(--banner-primary-border);
--_banner-text: var(--banner-primary-text);
}
[data-banner][data-intent='secondary'] {
--_banner-solid: var(--color-secondary-solid);
--_banner-solid-contrast: var(--color-secondary-contrast);
--_banner-track: var(--color-secondary-track);
--_banner-border: var(--color-secondary-border);
--_banner-text: var(--color-secondary-text);
--_banner-solid: var(--banner-secondary-solid);
--_banner-solid-contrast: var(--banner-secondary-contrast);
--_banner-track: var(--banner-secondary-track);
--_banner-border: var(--banner-secondary-border);
--_banner-text: var(--banner-secondary-text);
}
[data-banner][data-intent='neutral'] {
--_banner-solid: var(--color-neutral-solid);
--_banner-solid-contrast: var(--color-neutral-contrast);
--_banner-track: var(--color-neutral-track);
--_banner-border: var(--color-neutral-border);
--_banner-text: var(--color-neutral-text);
--_banner-solid: var(--banner-neutral-solid);
--_banner-solid-contrast: var(--banner-neutral-contrast);
--_banner-track: var(--banner-neutral-track);
--_banner-border: var(--banner-neutral-border);
--_banner-text: var(--banner-neutral-text);
}
[data-banner][data-intent='affirm'] {
--_banner-solid: var(--color-affirm-solid);
--_banner-solid-contrast: var(--color-affirm-contrast);
--_banner-track: var(--color-affirm-track);
--_banner-border: var(--color-affirm-border);
--_banner-text: var(--color-affirm-text);
--_banner-solid: var(--banner-affirm-solid);
--_banner-solid-contrast: var(--banner-affirm-contrast);
--_banner-track: var(--banner-affirm-track);
--_banner-border: var(--banner-affirm-border);
--_banner-text: var(--banner-affirm-text);
}
[data-banner][data-intent='fulfill'] {
--_banner-solid: var(--color-fulfill-solid);
--_banner-solid-contrast: var(--color-fulfill-contrast);
--_banner-track: var(--color-fulfill-track);
--_banner-border: var(--color-fulfill-border);
--_banner-text: var(--color-fulfill-text);
--_banner-solid: var(--banner-fulfill-solid);
--_banner-solid-contrast: var(--banner-fulfill-contrast);
--_banner-track: var(--banner-fulfill-track);
--_banner-border: var(--banner-fulfill-border);
--_banner-text: var(--banner-fulfill-text);
}
[data-banner][data-intent='risk'] {
--_banner-solid: var(--color-risk-solid);
--_banner-solid-contrast: var(--color-risk-contrast);
--_banner-track: var(--color-risk-track);
--_banner-border: var(--color-risk-border);
--_banner-text: var(--color-risk-text);
--_banner-solid: var(--banner-risk-solid);
--_banner-solid-contrast: var(--banner-risk-contrast);
--_banner-track: var(--banner-risk-track);
--_banner-border: var(--banner-risk-border);
--_banner-text: var(--banner-risk-text);
}
[data-banner][data-intent='threat'] {
--_banner-solid: var(--color-threat-solid);
--_banner-solid-contrast: var(--color-threat-contrast);
--_banner-track: var(--color-threat-track);
--_banner-border: var(--color-threat-border);
--_banner-text: var(--color-threat-text);
--_banner-solid: var(--banner-threat-solid);
--_banner-solid-contrast: var(--banner-threat-contrast);
--_banner-track: var(--banner-threat-track);
--_banner-border: var(--banner-threat-border);
--_banner-text: var(--banner-threat-text);
}
[data-banner][data-intent='loss'] {
--_banner-solid: var(--color-loss-solid);
--_banner-solid-contrast: var(--color-loss-contrast);
--_banner-track: var(--color-loss-track);
--_banner-border: var(--color-loss-border);
--_banner-text: var(--color-loss-text);
--_banner-solid: var(--banner-loss-solid);
--_banner-solid-contrast: var(--banner-loss-contrast);
--_banner-track: var(--banner-loss-track);
--_banner-border: var(--banner-loss-border);
--_banner-text: var(--banner-loss-text);
}
/* ── Variant treatment ─────────────────────────────────────────────── */

@ -2968,6 +2968,46 @@
--group-attached-overlap: 1px;
--banner-gap: var(--space-3);
--banner-radius: 0;
--banner-primary-solid: var(--color-primary-solid);
--banner-primary-contrast: var(--color-primary-contrast);
--banner-primary-track: var(--color-primary-track);
--banner-primary-border: var(--color-primary-border);
--banner-primary-text: var(--color-primary-text);
--banner-secondary-solid: var(--color-secondary-solid);
--banner-secondary-contrast: var(--color-secondary-contrast);
--banner-secondary-track: var(--color-secondary-track);
--banner-secondary-border: var(--color-secondary-border);
--banner-secondary-text: var(--color-secondary-text);
--banner-neutral-solid: var(--color-neutral-solid);
--banner-neutral-contrast: var(--color-neutral-contrast);
--banner-neutral-track: var(--color-neutral-track);
--banner-neutral-border: var(--color-neutral-border);
--banner-neutral-text: var(--color-neutral-text);
--banner-affirm-solid: var(--color-affirm-solid);
--banner-affirm-contrast: var(--color-affirm-contrast);
--banner-affirm-track: var(--color-affirm-track);
--banner-affirm-border: var(--color-affirm-border);
--banner-affirm-text: var(--color-affirm-text);
--banner-fulfill-solid: var(--color-fulfill-solid);
--banner-fulfill-contrast: var(--color-fulfill-contrast);
--banner-fulfill-track: var(--color-fulfill-track);
--banner-fulfill-border: var(--color-fulfill-border);
--banner-fulfill-text: var(--color-fulfill-text);
--banner-risk-solid: var(--color-risk-solid);
--banner-risk-contrast: var(--color-risk-contrast);
--banner-risk-track: var(--color-risk-track);
--banner-risk-border: var(--color-risk-border);
--banner-risk-text: var(--color-risk-text);
--banner-threat-solid: var(--color-threat-solid);
--banner-threat-contrast: var(--color-threat-contrast);
--banner-threat-track: var(--color-threat-track);
--banner-threat-border: var(--color-threat-border);
--banner-threat-text: var(--color-threat-text);
--banner-loss-solid: var(--color-loss-solid);
--banner-loss-contrast: var(--color-loss-contrast);
--banner-loss-track: var(--color-loss-track);
--banner-loss-border: var(--color-loss-border);
--banner-loss-text: var(--color-loss-text);
--container-width-xl: var(--layout-container-width-xl, 80rem);
--section-padding-block-sm: var(--space-8);
--section-padding-block-md: var(--space-12);

@ -3798,7 +3798,52 @@ export const THEME_BASE_RECIPE_TOKENS = {
// ─────────────────────────────────────────────────────────────────────
banner: {
gap: 'var(--space-3)',
radius: '0'
radius: '0',
// Per-intent palette forwarders → Banner is retintable via
// `recipes.banner.{intent}-{slot}` like every other color component
// (button/badge/toggle). banner.css reads these instead of the raw
// foundation `--color-{intent}-*` tokens, so a per-component recipe
// override actually reaches it. (audit P1-5)
'primary-solid': 'var(--color-primary-solid)',
'primary-contrast': 'var(--color-primary-contrast)',
'primary-track': 'var(--color-primary-track)',
'primary-border': 'var(--color-primary-border)',
'primary-text': 'var(--color-primary-text)',
'secondary-solid': 'var(--color-secondary-solid)',
'secondary-contrast': 'var(--color-secondary-contrast)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-border': 'var(--color-secondary-border)',
'secondary-text': 'var(--color-secondary-text)',
'neutral-solid': 'var(--color-neutral-solid)',
'neutral-contrast': 'var(--color-neutral-contrast)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-border': 'var(--color-neutral-border)',
'neutral-text': 'var(--color-neutral-text)',
'affirm-solid': 'var(--color-affirm-solid)',
'affirm-contrast': 'var(--color-affirm-contrast)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-border': 'var(--color-affirm-border)',
'affirm-text': 'var(--color-affirm-text)',
'fulfill-solid': 'var(--color-fulfill-solid)',
'fulfill-contrast': 'var(--color-fulfill-contrast)',
'fulfill-track': 'var(--color-fulfill-track)',
'fulfill-border': 'var(--color-fulfill-border)',
'fulfill-text': 'var(--color-fulfill-text)',
'risk-solid': 'var(--color-risk-solid)',
'risk-contrast': 'var(--color-risk-contrast)',
'risk-track': 'var(--color-risk-track)',
'risk-border': 'var(--color-risk-border)',
'risk-text': 'var(--color-risk-text)',
'threat-solid': 'var(--color-threat-solid)',
'threat-contrast': 'var(--color-threat-contrast)',
'threat-track': 'var(--color-threat-track)',
'threat-border': 'var(--color-threat-border)',
'threat-text': 'var(--color-threat-text)',
'loss-solid': 'var(--color-loss-solid)',
'loss-contrast': 'var(--color-loss-contrast)',
'loss-track': 'var(--color-loss-track)',
'loss-border': 'var(--color-loss-border)',
'loss-text': 'var(--color-loss-text)'
},
// ─────────────────────────────────────────────────────────────────────

Loading…
Cancel
Save

Powered by TurnKey Linux.