uix(color): abre `color` en metrics (featured icon) + form (submit/reset)

Dos aperturas por partes (`parts:` en recipe, forma `declarations:`):

- metrics-icon: los 5 slots del featured icon (track/solid/text/contrast/
  border) pasan de 8 bloques per-rol al forward compartido en la parte `icon`.
  Fuera el guard roles-only del wrapper (caia a neutral con cualquier valor
  no-rol); `plain` sigue sin estampar. Default primary estampado = identico.
  El flash de valor NO se toca: gradua por `data-event-intent` (sema), no por
  el prop `color`. `MetricsChartProps.color` queda cerrado: delega en la
  familia chart, que NO esta en la cola del handoff (hueco señalado aparte).
- form submit/reset: 4 slots (solid/solid-hover/border/contrast) en las DOS
  partes via el mismo forward. `'neutral'` (default de reset) NO estampa: la
  base action look (element bg + text ink) ES el neutral — estamparlo habria
  cambiado reset a solid grey. Submit default primary = identico.

Verificado en Chrome (probe getComputedStyle): metrics-icon risk 5/5 slots +
crimson + custom (contrast blanco derivado); form threat 4/4 + iris + custom;
reset sin estampa con look base intacto. check: 0 errores en tocados (total
76 = baseline); contract test 26/28 (2 pre-existentes proof-of-human).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 3 months ago
parent 8f4e568b25
commit 838b01a005

@ -1,5 +1,7 @@
<script lang="ts">
import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as Form from '$soma/components/form';
import type { FormResetProps } from './types';
@ -7,18 +9,38 @@
size = 'md',
variant = 'ghost',
color = 'neutral',
style,
children: bodyContent,
...rest
}: FormResetProps = $props();
const eidos = ActiveEidos.require();
const resolvedSize = $derived(eidos.resolve(size, 'md'));
// `color` accepts the full system: canonical → `data-color`, raw value →
// `data-color-custom` + the `--color-custom` seed. `'neutral'` (the default)
// stamps nothing — the base action look (element bg + text ink) is the neutral.
const colorAttrs = $derived(resolveComponentColor(color === 'neutral' ? undefined : color));
const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
</script>
{#if bodyContent}
<Form.Reset {...rest} data-size={resolvedSize} data-variant={variant} data-color={color}>
<Form.Reset
{...rest}
data-size={resolvedSize}
data-variant={variant}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={mergedStyle}
>
{@render bodyContent()}
</Form.Reset>
{:else}
<Form.Reset {...rest} data-size={resolvedSize} data-variant={variant} data-color={color} />
<Form.Reset
{...rest}
data-size={resolvedSize}
data-variant={variant}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={mergedStyle}
/>
{/if}

@ -1,5 +1,7 @@
<script lang="ts">
import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as Form from '$soma/components/form';
import type { FormSubmitProps } from './types';
@ -7,18 +9,38 @@
size = 'md',
variant = 'surface',
color = 'primary',
style,
children: bodyContent,
...rest
}: FormSubmitProps = $props();
const eidos = ActiveEidos.require();
const resolvedSize = $derived(eidos.resolve(size, 'md'));
// `color` accepts the full system: canonical → `data-color`, raw value →
// `data-color-custom` + the `--color-custom` seed. `'neutral'` stamps
// nothing — the base action look (element bg + text ink) is the neutral.
const colorAttrs = $derived(resolveComponentColor(color === 'neutral' ? undefined : color));
const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
</script>
{#if bodyContent}
<Form.Submit {...rest} data-size={resolvedSize} data-variant={variant} data-color={color}>
<Form.Submit
{...rest}
data-size={resolvedSize}
data-variant={variant}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={mergedStyle}
>
{@render bodyContent()}
</Form.Submit>
{:else}
<Form.Submit {...rest} data-size={resolvedSize} data-variant={variant} data-color={color} />
<Form.Submit
{...rest}
data-size={resolvedSize}
data-variant={variant}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={mergedStyle}
/>
{/if}

@ -136,60 +136,18 @@
--_form-action-font-size: var(--form-action-font-size-lg);
}
[data-form-submit][data-color='primary'],
[data-form-reset][data-color='primary'] {
--_form-action-bg: var(--color-primary-solid);
--_form-action-bg-hover: var(--color-primary-solid-hover);
--_form-action-border: var(--color-primary-border);
--_form-action-color: var(--color-primary-contrast);
}
[data-form-submit][data-color='secondary'],
[data-form-reset][data-color='secondary'] {
--_form-action-bg: var(--color-secondary-solid);
--_form-action-bg-hover: var(--color-secondary-solid-hover);
--_form-action-border: var(--color-secondary-border);
--_form-action-color: var(--color-secondary-contrast);
}
[data-form-submit][data-color='affirm'],
[data-form-reset][data-color='affirm'] {
--_form-action-bg: var(--color-affirm-solid);
--_form-action-bg-hover: var(--color-affirm-solid-hover);
--_form-action-border: var(--color-affirm-border);
--_form-action-color: var(--color-affirm-contrast);
}
[data-form-submit][data-color='fulfill'],
[data-form-reset][data-color='fulfill'] {
--_form-action-bg: var(--color-fulfill-solid);
--_form-action-bg-hover: var(--color-fulfill-solid-hover);
--_form-action-border: var(--color-fulfill-border);
--_form-action-color: var(--color-fulfill-contrast);
}
[data-form-submit][data-color='risk'],
[data-form-reset][data-color='risk'] {
--_form-action-bg: var(--color-risk-solid);
--_form-action-bg-hover: var(--color-risk-solid-hover);
--_form-action-border: var(--color-risk-border);
--_form-action-color: var(--color-risk-contrast);
}
[data-form-submit][data-color='threat'],
[data-form-reset][data-color='threat'] {
--_form-action-bg: var(--color-threat-solid);
--_form-action-bg-hover: var(--color-threat-solid-hover);
--_form-action-border: var(--color-threat-border);
--_form-action-color: var(--color-threat-contrast);
}
[data-form-submit][data-color='loss'],
[data-form-reset][data-color='loss'] {
--_form-action-bg: var(--color-loss-solid);
--_form-action-bg-hover: var(--color-loss-solid-hover);
--_form-action-border: var(--color-loss-border);
--_form-action-color: var(--color-loss-contrast);
/* Colour comes from the shared palette forward (`--_form-palette-*`, parts
`submit` + `reset`): any role / 33 scale / custom value fills the solid
action look. `'neutral'` never stamps `data-color` — the base action
defaults above (element bg + text ink) ARE the neutral. */
[data-form-submit][data-color],
[data-form-submit][data-color-custom],
[data-form-reset][data-color],
[data-form-reset][data-color-custom] {
--_form-action-bg: var(--_form-palette-solid);
--_form-action-bg-hover: var(--_form-palette-solid-hover);
--_form-action-border: var(--_form-palette-border);
--_form-action-color: var(--_form-palette-contrast);
}
[data-form-submit][data-variant='outline'],

@ -8,13 +8,13 @@ import type {
SubmitProps,
AutoFields
} from '$soma/components/form';
import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types';
import type { ComponentColorProp, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types';
export type FormSize = Extract<Size, 'xs' | 'sm' | 'md' | 'lg' | 'xl'>;
export type FormLayout = 'stack' | 'grid';
export type FormVariant = 'plain' | 'panel';
export type FormActionVariant = ControlVariant;
export type FormActionColor = ColorRole;
export type FormActionColor = ComponentColorProp;
export type FormProps = ProviderProps & {
/** Visual density for form spacing and action controls. @default 'md' */

@ -10,24 +10,34 @@
* with the root `size` via the recipe cascade, so drop a bare `<DollarSign />`
* in (it inherits `--icon-size`), not `<DollarSign size="sm" />`.
*/
import { COLOR_ROLES } from '$uix/eidos/lib/config-types';
import { composeInlineStyle } from '$uix/eidos/lib/style';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import type { MetricsIconProps } from './types';
let { variant = 'soft', shape = 'rounded', color = 'primary', children, ...rest }: MetricsIconProps =
$props();
let {
variant = 'soft',
shape = 'rounded',
color = 'primary',
style,
children,
...rest
}: MetricsIconProps = $props();
// Only canonical roles tint the container; anything else falls back to neutral.
const resolvedColor = $derived(
(COLOR_ROLES as readonly string[]).includes(color) ? color : 'neutral'
);
// `color` accepts the full system: canonical → `data-color`, raw value →
// `data-color-custom` + the `--color-custom` seed. `plain` renders the bare
// glyph — no container, no tint, no stamp.
const colorAttrs = $derived(resolveComponentColor(variant === 'plain' ? undefined : color));
const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
</script>
<span
data-metrics-icon=""
data-variant={variant}
data-shape={shape}
data-color={variant === 'plain' ? undefined : resolvedColor}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
aria-hidden="true"
style={mergedStyle}
{...rest}
>
{@render children?.()}

@ -324,60 +324,14 @@
color: var(--_mi-text);
}
/* Per-colour palette (one rule each; the variant rules above read these). */
[data-metrics-icon][data-color='primary'] {
--_mi-track: var(--color-primary-track);
--_mi-solid: var(--color-primary-solid);
--_mi-text: var(--color-primary-text);
--_mi-contrast: var(--color-primary-contrast);
--_mi-border: var(--color-primary-border);
}
[data-metrics-icon][data-color='secondary'] {
--_mi-track: var(--color-secondary-track);
--_mi-solid: var(--color-secondary-solid);
--_mi-text: var(--color-secondary-text);
--_mi-contrast: var(--color-secondary-contrast);
--_mi-border: var(--color-secondary-border);
}
[data-metrics-icon][data-color='neutral'] {
--_mi-track: var(--color-neutral-track);
--_mi-solid: var(--color-neutral-solid);
--_mi-text: var(--color-neutral-text);
--_mi-contrast: var(--color-neutral-contrast);
--_mi-border: var(--color-neutral-border);
}
[data-metrics-icon][data-color='affirm'] {
--_mi-track: var(--color-affirm-track);
--_mi-solid: var(--color-affirm-solid);
--_mi-text: var(--color-affirm-text);
--_mi-contrast: var(--color-affirm-contrast);
--_mi-border: var(--color-affirm-border);
}
[data-metrics-icon][data-color='fulfill'] {
--_mi-track: var(--color-fulfill-track);
--_mi-solid: var(--color-fulfill-solid);
--_mi-text: var(--color-fulfill-text);
--_mi-contrast: var(--color-fulfill-contrast);
--_mi-border: var(--color-fulfill-border);
}
[data-metrics-icon][data-color='risk'] {
--_mi-track: var(--color-risk-track);
--_mi-solid: var(--color-risk-solid);
--_mi-text: var(--color-risk-text);
--_mi-contrast: var(--color-risk-contrast);
--_mi-border: var(--color-risk-border);
}
[data-metrics-icon][data-color='threat'] {
--_mi-track: var(--color-threat-track);
--_mi-solid: var(--color-threat-solid);
--_mi-text: var(--color-threat-text);
--_mi-contrast: var(--color-threat-contrast);
--_mi-border: var(--color-threat-border);
}
[data-metrics-icon][data-color='loss'] {
--_mi-track: var(--color-loss-track);
--_mi-solid: var(--color-loss-solid);
--_mi-text: var(--color-loss-text);
--_mi-contrast: var(--color-loss-contrast);
--_mi-border: var(--color-loss-border);
/* Colour comes from the shared palette forward (`--_metrics-palette-*`, part
`icon`): any role / 33 scale / custom value fills the five slots the variant
rules above read. The no-stamp case (`plain`) keeps the base defaults. */
[data-metrics-icon][data-color],
[data-metrics-icon][data-color-custom] {
--_mi-track: var(--_metrics-palette-track);
--_mi-solid: var(--_metrics-palette-solid);
--_mi-text: var(--_metrics-palette-text);
--_mi-contrast: var(--_metrics-palette-contrast);
--_mi-border: var(--_metrics-palette-border);
}

@ -1,6 +1,12 @@
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import type { ChipVariant, ColorRole, ResponsiveProp, Size } from '$uix/eidos/lib/types';
import type {
ChipVariant,
ColorRole,
ComponentColorProp,
ResponsiveProp,
Size
} from '$uix/eidos/lib/types';
import type { SparklineProps } from '$uix/eidos/components/chart';
/**
@ -153,8 +159,11 @@ export type MetricsIconProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'>
variant?: MetricsIconVariant;
/** Container silhouette. @default 'circle' */
shape?: MetricsIconShape;
/** Featured-icon tint — a canonical `ColorRole`. @default 'primary' */
color?: ColorRole;
/**
* Featured-icon tint — any role / intent / 33 donor scales / raw CSS color.
* @default 'primary'
*/
color?: ComponentColorProp;
children?: Snippet;
};

@ -4295,6 +4295,20 @@
--_field-palette-text: var(--palette-text, var(--color-primary-text));
}
[data-form-submit], [data-form-reset] {
--_form-palette-solid: var(--color-primary-solid);
--_form-palette-solid-hover: var(--color-primary-solid-hover);
--_form-palette-border: var(--color-primary-border);
--_form-palette-contrast: var(--color-primary-contrast);
}
[data-form-submit][data-color], [data-form-submit][data-color-custom], [data-form-reset][data-color], [data-form-reset][data-color-custom] {
--_form-palette-border: var(--palette-border, var(--color-primary-border));
--_form-palette-solid: var(--palette-solid, var(--color-primary-solid));
--_form-palette-solid-hover: var(--palette-solid-hover, var(--color-primary-solid-hover));
--_form-palette-contrast: var(--palette-contrast, var(--color-primary-contrast));
}
[data-button] {
--button-palette-track: var(--button-primary-track);
--button-palette-element: var(--button-primary-element);
@ -4921,6 +4935,22 @@
--_tags-input-palette-text: var(--palette-text, var(--tags-input-primary-text));
}
[data-metrics-icon] {
--_metrics-palette-track: var(--color-primary-track);
--_metrics-palette-solid: var(--color-primary-solid);
--_metrics-palette-text: var(--color-primary-text);
--_metrics-palette-contrast: var(--color-primary-contrast);
--_metrics-palette-border: var(--color-primary-border);
}
[data-metrics-icon][data-color], [data-metrics-icon][data-color-custom] {
--_metrics-palette-track: var(--palette-track, var(--color-primary-track));
--_metrics-palette-border: var(--palette-border, var(--color-primary-border));
--_metrics-palette-solid: var(--palette-solid, var(--color-primary-solid));
--_metrics-palette-text: var(--palette-text, var(--color-primary-text));
--_metrics-palette-contrast: var(--palette-contrast, var(--color-primary-contrast));
}
[data-stepper] {
--_stepper-palette-solid: var(--stepper-primary-solid);
--_stepper-palette-track: var(--stepper-primary-track);

@ -1032,6 +1032,25 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'action-gap': 'var(--space-2)',
'action-radius': 'var(--radius-md)',
'action-border-width': 'var(--border-width)',
// Action palette forward (THM-2) — submit/reset tint through the shared
// layer (roles + 33 scales + custom). Host = primary (submit's stamped
// default); `'neutral'` stamps nothing and keeps the base action look.
'_palette-solid': {
parts: ['submit', 'reset'],
declarations: [{ value: 'var(--color-primary-solid)', scope: 'host' }]
},
'_palette-solid-hover': {
parts: ['submit', 'reset'],
declarations: [{ value: 'var(--color-primary-solid-hover)', scope: 'host' }]
},
'_palette-border': {
parts: ['submit', 'reset'],
declarations: [{ value: 'var(--color-primary-border)', scope: 'host' }]
},
'_palette-contrast': {
parts: ['submit', 'reset'],
declarations: [{ value: 'var(--color-primary-contrast)', scope: 'host' }]
},
'error-summary-padding': 'var(--space-3)',
'error-summary-border-width': 'var(--border-width)',
'error-summary-radius': 'var(--radius-md)',
@ -3062,9 +3081,10 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
}
},
metrics: {
// Metrics is SURFACELESS — no per-colour palette here. The Delta's colour
// comes from the composed <Badge>; the featured icon and the live flash
// tint use the canonical `--color-*` tokens directly.
// Metrics is SURFACELESS — the Delta's colour comes from the composed
// <Badge>; the live flash tint uses the canonical `--color-*` tokens
// directly (it grades by EVENT intent, not by the `color` prop). The
// featured icon routes through the shared palette forward below.
// ── Vertical rhythm between label / value / delta / description ──
'gap-xs': 'var(--space-1)',
'gap-sm': 'var(--space-1)',
@ -3093,6 +3113,30 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'icon-border-width': 'var(--border-width)',
'icon-bg': 'var(--color-surface-muted)',
'icon-color': 'var(--color-content-secondary)',
// Featured-icon palette forward (THM-2) — the five slots the variant rules
// read, on the `icon` part. Host = primary (the stamped default); shared
// layer routes roles + 33 scales + custom. `plain` stamps nothing and
// keeps the base `icon-bg` / `icon-color` defaults.
'_palette-track': {
parts: ['icon'],
declarations: [{ value: 'var(--color-primary-track)', scope: 'host' }]
},
'_palette-solid': {
parts: ['icon'],
declarations: [{ value: 'var(--color-primary-solid)', scope: 'host' }]
},
'_palette-text': {
parts: ['icon'],
declarations: [{ value: 'var(--color-primary-text)', scope: 'host' }]
},
'_palette-contrast': {
parts: ['icon'],
declarations: [{ value: 'var(--color-primary-contrast)', scope: 'host' }]
},
'_palette-border': {
parts: ['icon'],
declarations: [{ value: 'var(--color-primary-border)', scope: 'host' }]
},
// ── Label ──
'label-font-family': 'var(--style-label-font-family)',
'label-font-size-xs': 'var(--size-xxs-font-size)',

Loading…
Cancel
Save

Powered by TurnKey Linux.