fix(eidos): button tonal variants render bg/border + variants×colors gallery

The recipe defined per-color tonal tokens inconsistently — only fulfill/loss had
`track`/`border`, so soft/surface/outline rendered with no background or border
for the other 6 colors (primary/secondary/neutral/affirm/risk/threat):
`palette-track`/`palette-border` referenced undefined `--button-{color}-track`/
`-border`, which collapsed the background/border declarations. Added the missing
track+border to all 6 (mapped to `var(--color-{role}-track|border)` like
fulfill/loss); the recipe is now uniform (7 slots per color). Verified all 8
colors × soft/surface/outline in dark + light.

Demo: new "Gallery" tab — a 6 variants × 8 colors matrix of real <Button>s
(hierarchy via `color`, evaluative palette via `intent`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
active-uix
dev 4 months ago
parent e7a3d9085b
commit e5303f8217

@ -1605,22 +1605,30 @@
--button-transition-ease: var(--ease-default); --button-transition-ease: var(--ease-default);
--button-spinner-duration: 720ms; --button-spinner-duration: 720ms;
--button-disabled-opacity: 0.5; --button-disabled-opacity: 0.5;
--button-primary-track: var(--color-primary-track);
--button-primary-element: var(--color-primary-element); --button-primary-element: var(--color-primary-element);
--button-primary-border: var(--color-primary-border);
--button-primary-solid: var(--color-primary-solid); --button-primary-solid: var(--color-primary-solid);
--button-primary-solid-hover: var(--color-primary-solid-hover); --button-primary-solid-hover: var(--color-primary-solid-hover);
--button-primary-text: var(--color-primary-text); --button-primary-text: var(--color-primary-text);
--button-primary-contrast: var(--color-primary-contrast); --button-primary-contrast: var(--color-primary-contrast);
--button-secondary-track: var(--color-secondary-track);
--button-secondary-element: var(--color-secondary-element); --button-secondary-element: var(--color-secondary-element);
--button-secondary-border: var(--color-secondary-border);
--button-secondary-solid: var(--color-secondary-solid); --button-secondary-solid: var(--color-secondary-solid);
--button-secondary-solid-hover: var(--color-secondary-solid-hover); --button-secondary-solid-hover: var(--color-secondary-solid-hover);
--button-secondary-text: var(--color-secondary-text); --button-secondary-text: var(--color-secondary-text);
--button-secondary-contrast: var(--color-secondary-contrast); --button-secondary-contrast: var(--color-secondary-contrast);
--button-neutral-track: var(--color-neutral-track);
--button-neutral-element: var(--color-neutral-element); --button-neutral-element: var(--color-neutral-element);
--button-neutral-border: var(--color-neutral-border);
--button-neutral-solid: var(--color-neutral-solid); --button-neutral-solid: var(--color-neutral-solid);
--button-neutral-solid-hover: var(--color-neutral-solid-hover); --button-neutral-solid-hover: var(--color-neutral-solid-hover);
--button-neutral-text: var(--color-neutral-text); --button-neutral-text: var(--color-neutral-text);
--button-neutral-contrast: var(--color-neutral-contrast); --button-neutral-contrast: var(--color-neutral-contrast);
--button-affirm-track: var(--color-affirm-track);
--button-affirm-element: var(--color-affirm-element); --button-affirm-element: var(--color-affirm-element);
--button-affirm-border: var(--color-affirm-border);
--button-affirm-solid: var(--color-affirm-solid); --button-affirm-solid: var(--color-affirm-solid);
--button-affirm-solid-hover: var(--color-affirm-solid-hover); --button-affirm-solid-hover: var(--color-affirm-solid-hover);
--button-affirm-text: var(--color-affirm-text); --button-affirm-text: var(--color-affirm-text);
@ -1632,12 +1640,16 @@
--button-fulfill-solid-hover: var(--color-fulfill-solid-hover); --button-fulfill-solid-hover: var(--color-fulfill-solid-hover);
--button-fulfill-text: var(--color-fulfill-text); --button-fulfill-text: var(--color-fulfill-text);
--button-fulfill-contrast: var(--color-fulfill-contrast); --button-fulfill-contrast: var(--color-fulfill-contrast);
--button-risk-track: var(--color-risk-track);
--button-risk-element: var(--color-risk-element); --button-risk-element: var(--color-risk-element);
--button-risk-border: var(--color-risk-border);
--button-risk-solid: var(--color-risk-solid); --button-risk-solid: var(--color-risk-solid);
--button-risk-solid-hover: var(--color-risk-solid-hover); --button-risk-solid-hover: var(--color-risk-solid-hover);
--button-risk-text: var(--color-risk-text); --button-risk-text: var(--color-risk-text);
--button-risk-contrast: var(--color-risk-contrast); --button-risk-contrast: var(--color-risk-contrast);
--button-threat-track: var(--color-threat-track);
--button-threat-element: var(--color-threat-element); --button-threat-element: var(--color-threat-element);
--button-threat-border: var(--color-threat-border);
--button-threat-solid: var(--color-threat-solid); --button-threat-solid: var(--color-threat-solid);
--button-threat-solid-hover: var(--color-threat-solid-hover); --button-threat-solid-hover: var(--color-threat-solid-hover);
--button-threat-text: var(--color-threat-text); --button-threat-text: var(--color-threat-text);

@ -1512,23 +1512,34 @@ export const THEME_BASE_RECIPE_TOKENS = {
'transition-ease': 'var(--ease-default)', 'transition-ease': 'var(--ease-default)',
'spinner-duration': '720ms', 'spinner-duration': '720ms',
'disabled-opacity': '0.5', 'disabled-opacity': '0.5',
// Per-color palettes (8 system tokens) // Per-color palettes — 7 slots per color, uniform across all 8: track /
// element / border / solid / solid-hover / text / contrast. soft + surface
// + outline variants read track/border, so every color must define them
// (was only fulfill/loss — the others rendered with no bg/border).
'primary-track': 'var(--color-primary-track)',
'primary-element': 'var(--color-primary-element)', 'primary-element': 'var(--color-primary-element)',
'primary-border': 'var(--color-primary-border)',
'primary-solid': 'var(--color-primary-solid)', 'primary-solid': 'var(--color-primary-solid)',
'primary-solid-hover': 'var(--color-primary-solid-hover)', 'primary-solid-hover': 'var(--color-primary-solid-hover)',
'primary-text': 'var(--color-primary-text)', 'primary-text': 'var(--color-primary-text)',
'primary-contrast': 'var(--color-primary-contrast)', 'primary-contrast': 'var(--color-primary-contrast)',
'secondary-track': 'var(--color-secondary-track)',
'secondary-element': 'var(--color-secondary-element)', 'secondary-element': 'var(--color-secondary-element)',
'secondary-border': 'var(--color-secondary-border)',
'secondary-solid': 'var(--color-secondary-solid)', 'secondary-solid': 'var(--color-secondary-solid)',
'secondary-solid-hover': 'var(--color-secondary-solid-hover)', 'secondary-solid-hover': 'var(--color-secondary-solid-hover)',
'secondary-text': 'var(--color-secondary-text)', 'secondary-text': 'var(--color-secondary-text)',
'secondary-contrast': 'var(--color-secondary-contrast)', 'secondary-contrast': 'var(--color-secondary-contrast)',
'neutral-track': 'var(--color-neutral-track)',
'neutral-element': 'var(--color-neutral-element)', 'neutral-element': 'var(--color-neutral-element)',
'neutral-border': 'var(--color-neutral-border)',
'neutral-solid': 'var(--color-neutral-solid)', 'neutral-solid': 'var(--color-neutral-solid)',
'neutral-solid-hover': 'var(--color-neutral-solid-hover)', 'neutral-solid-hover': 'var(--color-neutral-solid-hover)',
'neutral-text': 'var(--color-neutral-text)', 'neutral-text': 'var(--color-neutral-text)',
'neutral-contrast': 'var(--color-neutral-contrast)', 'neutral-contrast': 'var(--color-neutral-contrast)',
'affirm-track': 'var(--color-affirm-track)',
'affirm-element': 'var(--color-affirm-element)', 'affirm-element': 'var(--color-affirm-element)',
'affirm-border': 'var(--color-affirm-border)',
'affirm-solid': 'var(--color-affirm-solid)', 'affirm-solid': 'var(--color-affirm-solid)',
'affirm-solid-hover': 'var(--color-affirm-solid-hover)', 'affirm-solid-hover': 'var(--color-affirm-solid-hover)',
'affirm-text': 'var(--color-affirm-text)', 'affirm-text': 'var(--color-affirm-text)',
@ -1540,12 +1551,16 @@ export const THEME_BASE_RECIPE_TOKENS = {
'fulfill-solid-hover': 'var(--color-fulfill-solid-hover)', 'fulfill-solid-hover': 'var(--color-fulfill-solid-hover)',
'fulfill-text': 'var(--color-fulfill-text)', 'fulfill-text': 'var(--color-fulfill-text)',
'fulfill-contrast': 'var(--color-fulfill-contrast)', 'fulfill-contrast': 'var(--color-fulfill-contrast)',
'risk-track': 'var(--color-risk-track)',
'risk-element': 'var(--color-risk-element)', 'risk-element': 'var(--color-risk-element)',
'risk-border': 'var(--color-risk-border)',
'risk-solid': 'var(--color-risk-solid)', 'risk-solid': 'var(--color-risk-solid)',
'risk-solid-hover': 'var(--color-risk-solid-hover)', 'risk-solid-hover': 'var(--color-risk-solid-hover)',
'risk-text': 'var(--color-risk-text)', 'risk-text': 'var(--color-risk-text)',
'risk-contrast': 'var(--color-risk-contrast)', 'risk-contrast': 'var(--color-risk-contrast)',
'threat-track': 'var(--color-threat-track)',
'threat-element': 'var(--color-threat-element)', 'threat-element': 'var(--color-threat-element)',
'threat-border': 'var(--color-threat-border)',
'threat-solid': 'var(--color-threat-solid)', 'threat-solid': 'var(--color-threat-solid)',
'threat-solid-hover': 'var(--color-threat-solid-hover)', 'threat-solid-hover': 'var(--color-threat-solid-hover)',
'threat-text': 'var(--color-threat-text)', 'threat-text': 'var(--color-threat-text)',
@ -3227,7 +3242,8 @@ export const THEME_BASE_RECIPE_TOKENS = {
// base theme (invisible against panels), so the track uses the same // base theme (invisible against panels), so the track uses the same
// visible-track mix as Progress: a light tint of content-primary on the // visible-track mix as Progress: a light tint of content-primary on the
// surface. Reads on white AND dark, and follows the theme. // surface. Reads on white AND dark, and follows the theme.
'track-bg': 'color-mix(in srgb, var(--color-content-primary) 12%, var(--color-surface-default))', 'track-bg':
'color-mix(in srgb, var(--color-content-primary) 12%, var(--color-surface-default))',
'range-bg': 'var(--color-primary-solid)', 'range-bg': 'var(--color-primary-solid)',
'thumb-radius': 'var(--radius-full)', 'thumb-radius': 'var(--radius-full)',
'thumb-border-width': 'var(--border-width)', 'thumb-border-width': 'var(--border-width)',

@ -30,7 +30,7 @@
x: Icon.X x: Icon.X
} as const; } as const;
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y'; type Tab = 'live' | 'gallery' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
let tab = $state<Tab>('live'); let tab = $state<Tab>('live');
const variants: ButtonVariant[] = ['solid', 'soft', 'surface', 'outline', 'ghost', 'plain']; const variants: ButtonVariant[] = ['solid', 'soft', 'surface', 'outline', 'ghost', 'plain'];
@ -40,6 +40,20 @@
const colors: Array<'primary' | 'secondary' | 'neutral'> = ['primary', 'secondary', 'neutral']; const colors: Array<'primary' | 'secondary' | 'neutral'> = ['primary', 'secondary', 'neutral'];
const types: Array<'button' | 'submit' | 'reset'> = ['button', 'submit', 'reset']; const types: Array<'button' | 'submit' | 'reset'> = ['button', 'submit', 'reset'];
// Gallery: all 8 color roles. Hierarchy (primary/secondary/neutral) rides the
// `color` prop; the evaluative palette (affirm/fulfill/risk/threat/loss) rides
// `intent`, which wins on data-color.
const gallery: Array<{ name: string; color?: 'primary' | 'secondary' | 'neutral'; intent: ButtonIntent }> = [
{ name: 'primary', color: 'primary', intent: 'neutral' },
{ name: 'secondary', color: 'secondary', intent: 'neutral' },
{ name: 'neutral', color: 'neutral', intent: 'neutral' },
{ name: 'affirm', intent: 'affirm' },
{ name: 'fulfill', intent: 'fulfill' },
{ name: 'risk', intent: 'risk' },
{ name: 'threat', intent: 'threat' },
{ name: 'loss', intent: 'loss' }
];
// ── Live state ──────────────────────────────────────────────────────── // ── Live state ────────────────────────────────────────────────────────
// Soma // Soma
let intent = $state<ButtonIntent>('neutral'); let intent = $state<ButtonIntent>('neutral');
@ -244,6 +258,9 @@
<!-- Tabs --> <!-- Tabs -->
<div data-uix-tabs role="tablist"> <div data-uix-tabs role="tablist">
<button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button> <button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button>
<button data-uix-tab data-active={tab === 'gallery'} onclick={() => (tab = 'gallery')}>
Gallery <span data-uix-tab-count>{variants.length}×{gallery.length}</span>
</button>
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}> <button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>
API <span data-uix-tab-count>2</span> API <span data-uix-tab-count>2</span>
</button> </button>
@ -455,6 +472,32 @@
</section> </section>
{/if} {/if}
{#if tab === 'gallery'}
<section data-uix-section>
<h2 data-uix-section-title>Variants × colors</h2>
<p data-uix-section-desc>
Every <span data-uix-layer-badge="eidos">eidos</span> variant across all 8 color roles.
Hierarchy (<code>primary</code> / <code>secondary</code> / <code>neutral</code>) rides the
<code>color</code> prop; the evaluative palette (<code>affirm</code> / <code>fulfill</code>
/ <code>risk</code> / <code>threat</code> / <code>loss</code>) rides <code>intent</code>,
which wins on <code>data-color</code>.
</p>
<div style="display: flex; flex-direction: column; gap: 0.85rem;">
{#each variants as v}
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<span
style="inline-size: 60px; flex-shrink: 0; font-family: monospace; font-size: 12px; color: var(--uix-text-faint);"
>{v}</span
>
{#each gallery as c}
<Button variant={v} color={c.color} intent={c.intent} size="sm">{c.name}</Button>
{/each}
</div>
{/each}
</div>
</section>
{/if}
{#if tab === 'api'} {#if tab === 'api'}
<section data-uix-section> <section data-uix-section>
<h2 data-uix-section-title>API reference</h2> <h2 data-uix-section-title>API reference</h2>

Loading…
Cancel
Save

Powered by TurnKey Linux.