diff --git a/src/uix/eidos/components/range-calendar/range-calendar.css b/src/uix/eidos/components/range-calendar/range-calendar.css index dd16d2f07..b2b432247 100644 --- a/src/uix/eidos/components/range-calendar/range-calendar.css +++ b/src/uix/eidos/components/range-calendar/range-calendar.css @@ -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]) { diff --git a/src/uix/eidos/components/range-calendar/range-calendar.svelte b/src/uix/eidos/components/range-calendar/range-calendar.svelte index 2ecaef1c8..f9ce2c412 100644 --- a/src/uix/eidos/components/range-calendar/range-calendar.svelte +++ b/src/uix/eidos/components/range-calendar/range-calendar.svelte @@ -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)); {#snippet children(snippetProps: RangeCalendarProviderSnippetProps)} {@render bodyContent?.(snippetProps)} diff --git a/src/uix/eidos/components/range-calendar/types.ts b/src/uix/eidos/components/range-calendar/types.ts index b2898e8b2..a59533287 100644 --- a/src/uix/eidos/components/range-calendar/types.ts +++ b/src/uix/eidos/components/range-calendar/types.ts @@ -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' */ diff --git a/src/uix/eidos/components/year-grid/types.ts b/src/uix/eidos/components/year-grid/types.ts index 392f99480..6bf132e3f 100644 --- a/src/uix/eidos/components/year-grid/types.ts +++ b/src/uix/eidos/components/year-grid/types.ts @@ -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 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' */ diff --git a/src/uix/eidos/components/year-grid/year-grid.css b/src/uix/eidos/components/year-grid/year-grid.css index fe82fe106..38253587b 100644 --- a/src/uix/eidos/components/year-grid/year-grid.css +++ b/src/uix/eidos/components/year-grid/year-grid.css @@ -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]) { diff --git a/src/uix/eidos/components/year-grid/year-grid.svelte b/src/uix/eidos/components/year-grid/year-grid.svelte index ea793db1e..a94e77983 100644 --- a/src/uix/eidos/components/year-grid/year-grid.svelte +++ b/src/uix/eidos/components/year-grid/year-grid.svelte @@ -1,6 +1,8 @@ {#snippet children(snippetProps: YearGridProviderSnippetProps)} {@render bodyContent?.(snippetProps)} diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index aaf62b4ac..e90be3665 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -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); diff --git a/src/uix/eidos/lib/recipes/base.ts b/src/uix/eidos/lib/recipes/base.ts index 84630f11b..8bfe68a77 100644 --- a/src/uix/eidos/lib/recipes/base.ts +++ b/src/uix/eidos/lib/recipes/base.ts @@ -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)',