From 845d6579d174ebe13792bb2032106b5adc936be8 Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 22 Jun 2026 15:44:15 +0200 Subject: [PATCH] =?UTF-8?q?feat(eidos):=20radius=20foundation=20=E2=80=94?= =?UTF-8?q?=20--radius-default=20+=20--radius-factor=20+=20scaling-respons?= =?UTF-8?q?ive=20(A2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First increment of the A2 (radius) phase. The radius scale was fixed px; now: - `--radius-default` (= var(--radius-sm)) — the archetype default radius for components without a radius reason of their own (Decisión 2 / §13). - `--radius-factor` (default 1) — a global Radix-style roundness knob; a theme sets it on :root to dial all corners up/down at once. - Radius responds to `--scaling`: each real radius emits `calc(base * var(--radius-factor) * var(--scaling))`. `none` (0) and `full` (the pill sentinel) stay verbatim. At factor=1 + scaling=100 both resolve to 1 → identical output (zero regression). Verified in-browser: md=6px default · --radius-default=4px · factor 1.5→9px (at :root) · scaling 1.1→6.6px · full=9999px. check 0. Pending (A2 increment 2): components consume --radius-default + adopt [data-shape-nest] (drop the hand-rolled calc(--radius-md − space)); panel-radius sub-decision (modal xl vs overlay lg). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/uix/eidos/generated/base.css | 12 +++++++----- src/uix/eidos/lib/render-css.ts | 29 ++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 750dd66cb..5da5ff9a0 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -154,13 +154,15 @@ --control-height-lg: calc(44px * var(--density-control-scale) * var(--scaling)); --control-height-xl: calc(52px * var(--density-control-scale) * var(--scaling)); --control-height-xxl: calc(60px * var(--density-control-scale) * var(--scaling)); + --radius-factor: 1; --radius-none: 0px; - --radius-sm: 4px; - --radius-md: 6px; - --radius-lg: 10px; - --radius-xl: 16px; - --radius-xxl: 20px; + --radius-sm: calc(4px * var(--radius-factor) * var(--scaling)); + --radius-md: calc(6px * var(--radius-factor) * var(--scaling)); + --radius-lg: calc(10px * var(--radius-factor) * var(--scaling)); + --radius-xl: calc(16px * var(--radius-factor) * var(--scaling)); + --radius-xxl: calc(20px * var(--radius-factor) * var(--scaling)); --radius-full: 9999px; + --radius-default: var(--radius-sm); --border-width-none: 0px; --border-width-thin: 1px; --border-width-medium: 2px; diff --git a/src/uix/eidos/lib/render-css.ts b/src/uix/eidos/lib/render-css.ts index 52247be9d..891506d2a 100644 --- a/src/uix/eidos/lib/render-css.ts +++ b/src/uix/eidos/lib/render-css.ts @@ -166,7 +166,7 @@ export function renderStaticCss(options: EidosConfig): string { primitives.controlHeight, 'density-control-scale' ) - appendRecordDeclarations(declarations, 'radius', primitives.radius) + appendRadiusDeclarations(declarations, primitives.radius) if (primitives.border) { appendBorderDeclarations(declarations, primitives.border) @@ -2248,6 +2248,33 @@ function appendScaledMetricDeclarations( } } +// Radius takes the global `--radius-factor` (Radix-style roundness knob, default 1) +// AND the `--scaling` zoom, multiplicatively — corners stay proportional when a theme +// dials roundness or the user zooms. At factor=1 + scaling=100 both are 1, so output +// equals the raw value (no regression). `none` (0) and `full` (the pill sentinel) are +// emitted verbatim — a factor/zoom must not curve a square corner nor shrink the pill. +// Also emits `--radius-default` (= sm), the archetype default for components without a +// radius reason of their own (Decisión 2 / §13). +function appendRadiusDeclarations( + declarations: string[], + radius: Record | undefined +): void { + if (!radius) return + declarations.push(cssVar('radius-factor', '1')) + for (const [name, value] of Object.entries(radius)) { + const raw = String(value).trim() + const num = parseFloat(raw) + const scalable = name !== 'none' && name !== 'full' && Number.isFinite(num) && num !== 0 + declarations.push( + cssVar( + `radius-${name}`, + scalable ? `calc(${raw} * var(--radius-factor) * var(--scaling))` : raw + ) + ) + } + declarations.push(cssVar('radius-default', 'var(--radius-sm)')) +} + function mergeThemeColor( primitiveScales: ColorScales | undefined, semanticColor: EidosConfig['semantics']['color'],