uix(color): abre `color` en year-grid + range-calendar (completa la familia calendar)

Mismo patrón mini-recipe que month-grid: recipe con 5 tokens `_palette-*`
(host = primary) -> el generador emite base default + forward
`[data-{c}][data-color], [data-{c}][data-color-custom]`; se quita la cascada
per-rol de la css y se renombran los consumidores accent -> palette; tipo ->
ComponentColorProp; wrapper vía `resolveComponentColor`.

- year-grid: espejo exacto de month-grid (5 slots, sin cross-refs).
- range-calendar: el END endpoint + `--calendar-range-end-*` bind a
  `--_range-calendar-palette-*`; el swap de START a secondary para affirm/fulfill
  (distinción de extremos) se conserva como regla per-rol (no es paleta).

Verificado en navegador (demo range-calendar): data-color=risk -> palette-solid
= range-end-solid = token risk compartido; affirm -> start = secondary; custom
#00b894 -> range-end = #00b894. check: 76 ajenos, 0 propios. Contract 26/28 (2 =
proof-of-human pre-existente).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 3 months ago
parent 712ea236ba
commit 889b03dae1

@ -34,11 +34,6 @@
--_calendar-control-size: var(--calendar-control-size-md);
--_calendar-day-size: var(--calendar-day-size-md);
--_calendar-font-size: var(--calendar-font-size-md);
--_calendar-accent-solid: var(--color-primary-solid);
--_calendar-accent-solid-hover: var(--color-primary-solid-hover);
--_calendar-accent-track: var(--color-primary-track);
--_calendar-accent-border: var(--color-primary-border);
--_calendar-accent-text: var(--color-primary-text);
/* Range endpoints — the calendar-surface public API `--calendar-range-*`. The
asymmetry below is DELIBERATE: START defaults live in the recipe (`:root`,
= affirm — the range "opens") so a theme can override them via config; the host
@ -46,9 +41,9 @@
MUST sit on the host where `--_calendar-accent-*` cascades — the recipe's `:root`
end defaults are the nominal public default (start is independently themeable,
end tracks the accent). */
--calendar-range-end-solid: var(--_calendar-accent-solid);
--calendar-range-end-solid-hover: var(--_calendar-accent-solid-hover);
--calendar-range-end-border: var(--_calendar-accent-solid);
--calendar-range-end-solid: var(--_range-calendar-palette-solid);
--calendar-range-end-solid-hover: var(--_range-calendar-palette-solid-hover);
--calendar-range-end-border: var(--_range-calendar-palette-solid);
--calendar-range-end-text: var(--calendar-day-selected-color);
display: inline-flex;
@ -100,73 +95,19 @@
box-shadow: none;
}
/* ── Color cascades ─────────────────────────────────────────────────── */
[data-range-calendar][data-color='secondary'] {
--_calendar-accent-solid: var(--color-secondary-solid);
--_calendar-accent-solid-hover: var(--color-secondary-solid-hover);
--_calendar-accent-track: var(--color-secondary-track);
--_calendar-accent-border: var(--color-secondary-border);
--_calendar-accent-text: var(--color-secondary-text);
}
[data-range-calendar][data-color='neutral'] {
--_calendar-accent-solid: var(--color-neutral-solid);
--_calendar-accent-solid-hover: var(--color-neutral-solid-hover);
--_calendar-accent-track: var(--color-neutral-track);
--_calendar-accent-border: var(--color-neutral-border);
--_calendar-accent-text: var(--color-neutral-text);
}
[data-range-calendar][data-color='affirm'] {
--_calendar-accent-solid: var(--color-affirm-solid);
--_calendar-accent-solid-hover: var(--color-affirm-solid-hover);
--_calendar-accent-track: var(--color-affirm-track);
--_calendar-accent-border: var(--color-affirm-border);
--_calendar-accent-text: var(--color-affirm-text);
/* When the end accent IS `affirm`, swap the start endpoint to
secondary so the two endpoints stay visually distinct. */
--calendar-range-start-solid: var(--color-secondary-solid);
--calendar-range-start-solid-hover: var(--color-secondary-solid-hover);
--calendar-range-start-border: var(--color-secondary-border);
--calendar-range-start-text: var(--color-secondary-contrast);
}
/* ── Endpoint distinctness (per-role START swap) ─────────────────────────
The END endpoint tracks the palette (`--_range-calendar-palette-*`, driven by
the shared forward for roles / 33 scales / custom). When the palette resolves
to affirm/fulfill, swap the START endpoint to secondary so the two endpoints
never blend — a distinctness refinement, not part of the palette. */
[data-range-calendar][data-color='affirm'],
[data-range-calendar][data-color='fulfill'] {
--_calendar-accent-solid: var(--color-fulfill-solid);
--_calendar-accent-solid-hover: var(--color-fulfill-solid-hover);
--_calendar-accent-track: var(--color-fulfill-track);
--_calendar-accent-border: var(--color-fulfill-border);
--_calendar-accent-text: var(--color-fulfill-text);
--calendar-range-start-solid: var(--color-secondary-solid);
--calendar-range-start-solid-hover: var(--color-secondary-solid-hover);
--calendar-range-start-border: var(--color-secondary-border);
--calendar-range-start-text: var(--color-secondary-contrast);
}
[data-range-calendar][data-color='risk'] {
--_calendar-accent-solid: var(--color-risk-solid);
--_calendar-accent-solid-hover: var(--color-risk-solid-hover);
--_calendar-accent-track: var(--color-risk-track);
--_calendar-accent-border: var(--color-risk-border);
--_calendar-accent-text: var(--color-risk-text);
}
[data-range-calendar][data-color='threat'] {
--_calendar-accent-solid: var(--color-threat-solid);
--_calendar-accent-solid-hover: var(--color-threat-solid-hover);
--_calendar-accent-track: var(--color-threat-track);
--_calendar-accent-border: var(--color-threat-border);
--_calendar-accent-text: var(--color-threat-text);
}
[data-range-calendar][data-color='loss'] {
--_calendar-accent-solid: var(--color-loss-solid);
--_calendar-accent-solid-hover: var(--color-loss-solid-hover);
--_calendar-accent-track: var(--color-loss-track);
--_calendar-accent-border: var(--color-loss-border);
--_calendar-accent-text: var(--color-loss-text);
}
/* ── Header + nav controls ──────────────────────────────────────────── */
[data-range-calendar-header] {
display: grid;
@ -277,7 +218,7 @@
[data-range-calendar-cell][data-in-range],
[data-range-calendar-cell][data-in-preview-range] {
background: var(--_calendar-accent-track);
background: var(--_range-calendar-palette-track);
}
/* ── Day cells + range-specific states ──────────────────────────────── */
@ -370,21 +311,21 @@
[data-range-calendar-day][data-in-range]:not([data-selected]) {
border-color: transparent;
background: transparent;
color: var(--_calendar-accent-text);
color: var(--_range-calendar-palette-text);
}
[data-range-calendar-day][data-preview-start],
[data-range-calendar-day][data-preview-end],
[data-range-calendar-day][data-in-preview-range]:not([data-selected]) {
border-color: var(--_calendar-accent-border);
border-color: var(--_range-calendar-palette-border);
background: transparent;
color: var(--_calendar-accent-text);
color: var(--_range-calendar-palette-text);
}
[data-range-calendar-day][data-focused]:not([data-selected]) {
border-color: var(--_calendar-accent-border);
background: var(--_calendar-accent-track);
color: var(--_calendar-accent-text);
border-color: var(--_range-calendar-palette-border);
background: var(--_range-calendar-palette-track);
color: var(--_range-calendar-palette-text);
}
[data-range-calendar-day][data-today]:not([data-selected]) {

@ -6,6 +6,8 @@
* Range-state machine (start → preview → end) is owned by soma.
*/
import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as RangeCalendar from '$soma/components/range-calendar';
import { setCalendarVisual } from '../calendar/visual-context';
import type { RangeCalendarProps, RangeCalendarProviderSnippetProps } from './types';
@ -16,6 +18,7 @@
color = 'primary',
value = $bindable(),
placeholder = $bindable(),
style,
children: bodyContent,
...rest
}: RangeCalendarProps = $props();
@ -24,6 +27,10 @@
const resolvedSize = $derived(eidos.resolve(size, 'md'));
// The shared month/year select popovers (MonthGrid/YearGrid) inherit this size.
setCalendarVisual({ getSize: () => resolvedSize });
// `color` accepts the full system: canonical → `data-color`, raw value →
// `data-color-custom` + the `--color-custom` seed (shared derivation).
const colorAttrs = $derived(resolveComponentColor(color));
const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
</script>
<RangeCalendar.Provider
@ -32,7 +39,9 @@
bind:placeholder
data-size={resolvedSize}
data-variant={variant}
data-color={color}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={mergedStyle}
>
{#snippet children(snippetProps: RangeCalendarProviderSnippetProps)}
{@render bodyContent?.(snippetProps)}

@ -22,7 +22,7 @@ import type {
RangeCalendarYearSelectSnippetProps
} from '$soma/components/range-calendar';
import type { DateValue } from '$libs/days';
import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types';
import type { ComponentColorProp, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types';
/**
* RangeCalendar visual size — four-step subset of the canonical scale,
@ -47,7 +47,7 @@ export type RangeCalendarVariant = ControlVariant;
* is `affirm` or `fulfill` — those swap the start to `secondary` so the
* two endpoints stay visually distinct (see range-calendar.css).
*/
export type RangeCalendarColor = ColorRole;
export type RangeCalendarColor = ComponentColorProp;
export type RangeCalendarProps = SomaRangeCalendarProps & {
/** Visual size for day cells, nav buttons and select controls. @default 'md' */

@ -12,11 +12,11 @@ import type {
YearValue,
YearPlaceholder
} from '$soma/components/year-grid'
import type { ColorRole, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types'
import type { ComponentColorProp, ControlVariant, ResponsiveProp, Size } from '$uix/eidos/lib/types'
export type YearGridSize = Extract<Size, 'xs' | 'sm' | 'md' | 'lg'>
export type YearGridVariant = ControlVariant
export type YearGridColor = ColorRole
export type YearGridColor = ComponentColorProp
export type YearGridProps = SomaYearGridProps & {
/** Visual size for cells and nav buttons. @default 'md' */

@ -5,11 +5,6 @@
--_year-grid-control-size: var(--calendar-control-size-md);
--_year-grid-cell-size: var(--calendar-day-size-md);
--_year-grid-font-size: var(--calendar-font-size-md);
--_year-grid-accent-solid: var(--color-primary-solid);
--_year-grid-accent-solid-hover: var(--color-primary-solid-hover);
--_year-grid-accent-track: var(--color-primary-track);
--_year-grid-accent-border: var(--color-primary-border);
--_year-grid-accent-text: var(--color-primary-text);
display: inline-flex;
flex-direction: column;
@ -61,62 +56,6 @@
box-shadow: none;
}
[data-year-grid][data-color='secondary'] {
--_year-grid-accent-solid: var(--color-secondary-solid);
--_year-grid-accent-solid-hover: var(--color-secondary-solid-hover);
--_year-grid-accent-track: var(--color-secondary-track);
--_year-grid-accent-border: var(--color-secondary-border);
--_year-grid-accent-text: var(--color-secondary-text);
}
[data-year-grid][data-color='neutral'] {
--_year-grid-accent-solid: var(--color-neutral-solid);
--_year-grid-accent-solid-hover: var(--color-neutral-solid-hover);
--_year-grid-accent-track: var(--color-neutral-track);
--_year-grid-accent-border: var(--color-neutral-border);
--_year-grid-accent-text: var(--color-neutral-text);
}
[data-year-grid][data-color='affirm'] {
--_year-grid-accent-solid: var(--color-affirm-solid);
--_year-grid-accent-solid-hover: var(--color-affirm-solid-hover);
--_year-grid-accent-track: var(--color-affirm-track);
--_year-grid-accent-border: var(--color-affirm-border);
--_year-grid-accent-text: var(--color-affirm-text);
}
[data-year-grid][data-color='fulfill'] {
--_year-grid-accent-solid: var(--color-fulfill-solid);
--_year-grid-accent-solid-hover: var(--color-fulfill-solid-hover);
--_year-grid-accent-track: var(--color-fulfill-track);
--_year-grid-accent-border: var(--color-fulfill-border);
--_year-grid-accent-text: var(--color-fulfill-text);
}
[data-year-grid][data-color='risk'] {
--_year-grid-accent-solid: var(--color-risk-solid);
--_year-grid-accent-solid-hover: var(--color-risk-solid-hover);
--_year-grid-accent-track: var(--color-risk-track);
--_year-grid-accent-border: var(--color-risk-border);
--_year-grid-accent-text: var(--color-risk-text);
}
[data-year-grid][data-color='threat'] {
--_year-grid-accent-solid: var(--color-threat-solid);
--_year-grid-accent-solid-hover: var(--color-threat-solid-hover);
--_year-grid-accent-track: var(--color-threat-track);
--_year-grid-accent-border: var(--color-threat-border);
--_year-grid-accent-text: var(--color-threat-text);
}
[data-year-grid][data-color='loss'] {
--_year-grid-accent-solid: var(--color-loss-solid);
--_year-grid-accent-solid-hover: var(--color-loss-solid-hover);
--_year-grid-accent-track: var(--color-loss-track);
--_year-grid-accent-border: var(--color-loss-border);
--_year-grid-accent-text: var(--color-loss-text);
}
[data-year-grid-header] {
display: grid;
grid-template-columns:
@ -226,20 +165,20 @@
}
[data-year-grid-cell][data-selected] {
border-color: var(--_year-grid-accent-solid);
background: var(--_year-grid-accent-solid);
border-color: var(--_year-grid-palette-solid);
background: var(--_year-grid-palette-solid);
color: var(--calendar-day-selected-color);
}
[data-year-grid-cell][data-selected]:hover {
border-color: var(--_year-grid-accent-solid-hover);
background: var(--_year-grid-accent-solid-hover);
border-color: var(--_year-grid-palette-solid-hover);
background: var(--_year-grid-palette-solid-hover);
}
[data-year-grid-cell][data-focused]:not([data-selected]) {
border-color: var(--_year-grid-accent-border);
background: var(--_year-grid-accent-track);
color: var(--_year-grid-accent-text);
border-color: var(--_year-grid-palette-border);
background: var(--_year-grid-palette-track);
color: var(--_year-grid-palette-text);
}
[data-year-grid-cell][data-current]:not([data-selected]) {

@ -1,6 +1,8 @@
<script lang="ts">
import './year-grid.css';
import { ActiveEidos } from '$uix/eidos';
import { composeInlineStyle } from '$uix/eidos/lib/style';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as YearGrid from '$soma/components/year-grid';
import type { YearGridProps, YearGridProviderSnippetProps } from './types';
@ -10,12 +12,17 @@
color = 'primary',
value = $bindable(),
placeholder = $bindable(),
style,
children: bodyContent,
...rest
}: YearGridProps = $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 (shared derivation).
const colorAttrs = $derived(resolveComponentColor(color));
const mergedStyle = $derived(composeInlineStyle(style, colorAttrs.customStyle));
</script>
<YearGrid.Provider
@ -24,7 +31,9 @@
bind:placeholder
data-size={resolvedSize}
data-variant={variant}
data-color={color}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={mergedStyle}
>
{#snippet children(snippetProps: YearGridProviderSnippetProps)}
{@render bodyContent?.(snippetProps)}

@ -4338,6 +4338,38 @@
--_month-grid-palette-text: var(--palette-text, var(--color-primary-text));
}
[data-year-grid] {
--_year-grid-palette-solid: var(--color-primary-solid);
--_year-grid-palette-solid-hover: var(--color-primary-solid-hover);
--_year-grid-palette-track: var(--color-primary-track);
--_year-grid-palette-border: var(--color-primary-border);
--_year-grid-palette-text: var(--color-primary-text);
}
[data-year-grid][data-color], [data-year-grid][data-color-custom] {
--_year-grid-palette-track: var(--palette-track, var(--color-primary-track));
--_year-grid-palette-border: var(--palette-border, var(--color-primary-border));
--_year-grid-palette-solid: var(--palette-solid, var(--color-primary-solid));
--_year-grid-palette-solid-hover: var(--palette-solid-hover, var(--color-primary-solid-hover));
--_year-grid-palette-text: var(--palette-text, var(--color-primary-text));
}
[data-range-calendar] {
--_range-calendar-palette-solid: var(--color-primary-solid);
--_range-calendar-palette-solid-hover: var(--color-primary-solid-hover);
--_range-calendar-palette-track: var(--color-primary-track);
--_range-calendar-palette-border: var(--color-primary-border);
--_range-calendar-palette-text: var(--color-primary-text);
}
[data-range-calendar][data-color], [data-range-calendar][data-color-custom] {
--_range-calendar-palette-track: var(--palette-track, var(--color-primary-track));
--_range-calendar-palette-border: var(--palette-border, var(--color-primary-border));
--_range-calendar-palette-solid: var(--palette-solid, var(--color-primary-solid));
--_range-calendar-palette-solid-hover: var(--palette-solid-hover, var(--color-primary-solid-hover));
--_range-calendar-palette-text: var(--palette-text, var(--color-primary-text));
}
[data-calendar] {
--_calendar-palette-solid: var(--color-primary-solid);
--_calendar-palette-solid-hover: var(--color-primary-solid-hover);

@ -1567,6 +1567,27 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'_palette-border': { value: 'var(--color-primary-border)', scope: 'host' },
'_palette-text': { value: 'var(--color-primary-text)', scope: 'host' }
},
'year-grid': {
// Palette forward (THM-2) — same as month-grid: host default = primary,
// shared layer routes roles + 33 scales + custom into `--_year-grid-palette-*`.
'_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' },
'_palette-solid-hover': { value: 'var(--color-primary-solid-hover)', scope: 'host' },
'_palette-track': { value: 'var(--color-primary-track)', scope: 'host' },
'_palette-border': { value: 'var(--color-primary-border)', scope: 'host' },
'_palette-text': { value: 'var(--color-primary-text)', scope: 'host' }
},
'range-calendar': {
// Palette forward (THM-2): the END accent + `--calendar-range-end-*` bind to
// `--_range-calendar-palette-*`; host default = primary, shared layer routes
// roles + 33 scales + custom. The affirm/fulfill START-endpoint swap stays a
// per-role rule in range-calendar.css (it's a distinctness refinement, not
// palette). (Was a per-role cascade over `--_calendar-accent-*`.)
'_palette-solid': { value: 'var(--color-primary-solid)', scope: 'host' },
'_palette-solid-hover': { value: 'var(--color-primary-solid-hover)', scope: 'host' },
'_palette-track': { value: 'var(--color-primary-track)', scope: 'host' },
'_palette-border': { value: 'var(--color-primary-border)', scope: 'host' },
'_palette-text': { value: 'var(--color-primary-text)', scope: 'host' }
},
calendar: {
gap: 'var(--space-3)',
'padding-xs': 'var(--space-2)',

Loading…
Cancel
Save

Powered by TurnKey Linux.