Prisma

Radius Reform — Gap Analysis & Proposal

Radius hiện tại là static (trỏ thẳng tới primitive), trong khi spacing là dynamic (calc(var(--spacing) * N)). Khi chuyển density, spacing co giãn nhưng radius đứng yên → mất đồng bộ.

1. Gap Analysis

Gap 1: Radius static, Spacing dynamic

text
Spacing: calc(var(--spacing) * 4)  → 12/16/20px khi thay --spacing
Radius:  var(--radius-sm)          → LUÔN 12px, bất kể density

Khi demo switch compact(3px) / comfortable(4px) / spacious(5px), padding/gap thay đổi mượt mà nhưng mọi border-radius đứng yên.

Gap 2: --comp-radius = --group-radius-default trên Mobile

Cả hai cùng trỏ var(--radius-sm) = 12px → mất concentric hierarchy.

text
Group:  border-radius: var(--group-radius-default) = 12px
  └ Comp: border-radius: var(--comp-radius)        = 12px  ← CÙNG GIÁ TRỊ!

Concentric rule yêu cầu: R_inner = R_outer − padding = 12 − 16 = −4 → phải giảm 1 step, nhưng hiện tại không giảm.

Gap 3: lint-css.js mapping sai

js
// HIỆN TẠI — SAI:
'4px':  'var(--radius-sm)',   // radius-sm = 12px, KHÔNG PHẢI 4px
'6px':  'var(--radius-md)',   // radius-md = 16px, KHÔNG PHẢI 6px
'8px':  'var(--radius-md)',   // radius-md = 16px, KHÔNG PHẢI 8px
'12px': 'var(--radius-lg)',   // radius-lg = 20px, KHÔNG PHẢI 12px
'16px': 'var(--radius-xl)',   // radius-xl = 24px, KHÔNG PHẢI 16px

Mapping này theo Tailwind v3 mặc định, không match primitive scale thực tế của Prisma DS.

Gap 4: Thiếu primitive cho 4px / 6px

Scale bắt đầu từ --radius-xs = 8px. Không có token cho giá trị nhỏ hơn.

Gap 5: Hardcode 4px trong demo

demo-app.html line 354, 359: border-radius: var(--comp-radius) trên spending bar → không có token nào match.


2. Đề xuất: Radius Formula-Based (giống Spacing)

Ý tưởng

Chuyển radius từ static primitive reference sang formula-based dùng calc(var(--spacing) * N), giống spacing.

css
/* TRƯỚC (static): */
--comp-radius: var(--radius-sm);        /* LUÔN 12px */

/* SAU (formula): */
--comp-radius: calc(var(--spacing) * 3);  /* 9/12/15px theo density */

Radius Multiplier Table

Tier Token Multiplier Compact (3px) Comfortable (4px) Spacious (5px)
--page-radius-default ×6 18px 24px 30px
--section-radius-default ×4 12px 16px 20px
--section-radius-small ×3 9px 12px 15px
--group-radius-default ×3 9px 12px 15px
--group-radius-small ×2 6px 8px 10px
--comp-radius ×2 6px 8px 10px
--comp-radius-capsule 9999px

Concentric Check (Comfortable, 4px)

Parent → Child R_outer Padding R_concentric R_actual ✓?
Page → Section 24px 24px 0 16px
Section → Group 16px 20px −4 → 0 12px
Group → Comp 12px 24px −12 → 0 8px FIX: giờ comp < group

Concentric Check (Compact, 3px)

Parent → Child R_outer Padding R_concentric R_actual ✓?
Page → Section 18px 18px 0 12px
Section → Group 12px 15px −3 → 0 9px
Group → Comp 9px 18px −9 → 0 6px

Web density — giữ giá trị lớn hơn

Tier Token Multiplier Web (4px) Web sẽ ra
--page-radius-default ×6 24px = hiện tại (radius-xl)
--section-radius-default ×5 20px ≈ hiện tại (radius-xl=24, chênh 4px)
--group-radius-default ×4 16px = hiện tại (radius-md)
--comp-radius ×3 12px = hiện tại (radius-sm)

Web default giữ nguyên hoặc gần bằng giá trị hiện tại → không breaking change.


3. Tác động & Files cần sửa

Token JSON

File Thay đổi
mobile.tokens.json Radius → calc(var(--spacing) * N)
web.tokens.json Radius → calc(var(--spacing) * N)

CSS

File Thay đổi
variables.css Auto-generated bởi sync-tokens.js
primitives.css Giữ nguyên — primitive scale vẫn tồn tại cho reference

Scripts

File Thay đổi
lint-css.js Fix BORDER_RADIUS_MAP cho đúng giá trị thực
sync-tokens.js Không cần đổi — nó đã emit $value as-is

Demos

File Thay đổi
demo-app.html Xóa hardcoded 4px, dùng var(--comp-radius) hoặc var(--radius-xs)

Style overrides (ios17, liquid)

File Ghi chú
ios17.json Giữ own border-radius object — là style-specific, không dùng density formula
liquid.json Tương tự

4. lint-css.js Fix — Correct Mapping

js
// SAU (đúng với Prisma primitive scale):
const BORDER_RADIUS_MAP = {
    '0':     'var(--radius-none) or 0',
    '4px':   'var(--radius-xs) or var(--comp-radius)',  // radius-xs = 8px (gần nhất)
    '6px':   'var(--radius-xs)',                        
    '8px':   'var(--radius-xs)',                        // radius-xs = 8px ✓
    '12px':  'var(--radius-sm)',                        // radius-sm = 12px ✓
    '16px':  'var(--radius-md)',                        // radius-md = 16px ✓
    '20px':  'var(--radius-lg)',                        // radius-lg = 20px ✓
    '24px':  'var(--radius-xl)',                        // radius-xl = 24px ✓
    '28px':  'var(--radius-2xl)',                       // radius-2xl = 28px ✓
    '32px':  'var(--radius-3xl)',                       // radius-3xl = 32px ✓
    '50%':   'var(--radius-full)',
    '9999px':'var(--radius-full)',
};

5. Quyết định cần bạn

[!IMPORTANT] Chọn 1 trong 2 hướng:

Option A: Formula-Based (recommended)

  • Radius = calc(var(--spacing) * N) → auto-scale với density
  • Con: giá trị có thể lẻ (9px, 15px) — nhưng trình duyệt render sub-pixel radius rất tốt
  • Pro: đồng bộ hoàn toàn với spacing, chỉ thay --spacing là xong

Option B: Step-Down Mapping (conservative)

  • Mỗi density profile chọn step khác trên primitive scale
  • Mobile: comp→xs(8), group→sm(12), section→md(16)
  • Web giữ nguyên: comp→sm(12), group→md(16), section→xl(24)
  • Con: không auto-scale, phải update thủ công cho mỗi density preset mới
  • Pro: giá trị luôn "đẹp" (8, 12, 16, 24...)

Bất kể chọn A hay B — lint-css.js cần fix mapping ngay.