diff --git a/.ua/visual-baseline.json b/.ua/visual-baseline.json index 615a992..922ee69 100644 --- a/.ua/visual-baseline.json +++ b/.ua/visual-baseline.json @@ -1,15 +1,16 @@ { "$schema": "视觉漂移基线:五类硬编码视觉字面量的允许上限,任一类超出即 ua audit:visual 退出码 1;迁移后用 --baseline 重写", "file": "packages/skin/ui/ui.css", - "commit": "a9a084c", - "generatedAt": "2026-10-26T09:17:07.493Z", + "commit": "320dac3", + "generatedAt": "2026-10-26T09:21:19.963Z", "lines": 4758, - "total": 27, + "total": 40, "counts": { "font-size-literal": 5, "duration-literal": 9, "shadow-literal": 9, "radius-literal": 0, - "focus-ring-variant": 4 + "focus-ring-variant": 4, + "space-literal": 13 } } diff --git a/llms-full.txt b/llms-full.txt index c80c196..0fdb675 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2154,6 +2154,10 @@ node packages/tools/cli/bin/ua.js |docs|tokens - --ua-space-2: 8px - --ua-space-3: 12px - --ua-space-4: 16px +- --ua-space-05: 2px +- --ua-space-15: 6px +- --ua-space-25: 10px +- --ua-space-35: 14px - --ua-space-table-cell: var(--ua-space-2) var(--ua-space-3) - --ua-font-size-xs: 11px - --ua-font-size-sm: 12px diff --git a/packages/design/tokens/tokens.css b/packages/design/tokens/tokens.css index 41e8f40..3203ea2 100644 --- a/packages/design/tokens/tokens.css +++ b/packages/design/tokens/tokens.css @@ -24,7 +24,11 @@ --ua-space-1: 4px; /* 间距 1 档:图标与文字之间、紧邻元素 */ --ua-space-2: 8px; /* 间距 2 档:控件内边距、同组元素间隔 */ --ua-space-3: 12px; /* 间距 3 档:表单行内边距、卡片内边距 */ - --ua-space-4: 16px; /* 间距 4 档:区块内边距、卡片间距 */ + --ua-space-4: 16px; + --ua-space-05: 2px; /* 半步:微型内距(紧凑徽标内衬、选中点偏移) */ + --ua-space-15: 6px; /* 一档半:紧凑控件内距(小按钮 / 标签) */ + --ua-space-25: 10px; /* 二档半:标准控件内距(介于 8 与 12 之间的既有主流值) */ + --ua-space-35: 14px; /* 三档半:宽松行内距(列表行 / 菜单行) */ /* 间距 4 档:区块内边距、卡片间距 */ --ua-space-table-cell: var(--ua-space-2) var(--ua-space-3); /* 表格单元格内边距:上下 2 档 / 左右 3 档 */ /* ---- 字号:五档主轴 + 两档展示字号(10/11px 统一归入 xs,避免单独成档) ---- */ diff --git a/packages/design/tokens/tokens.json b/packages/design/tokens/tokens.json index e11b1ba..0d508db 100644 --- a/packages/design/tokens/tokens.json +++ b/packages/design/tokens/tokens.json @@ -51,7 +51,11 @@ "1": "4px", "2": "8px", "3": "12px", - "4": "16px" + "4": "16px", + "15": "6px", + "25": "10px", + "35": "14px", + "05": "2px" }, "fontSize": { "xs": "11px", diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 437d5d3..e3bde7f 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -7,7 +7,7 @@ outline-offset: 2px; } .ua-btn { - padding: 6px 14px; + padding: var(--ua-space-15) var(--ua-space-35); border: 1px solid var(--ua-color-accent); border-radius: var(--ua-radius-md); background: var(--ua-color-accent); @@ -39,7 +39,7 @@ } .ua-input { min-width: 200px; - padding: 6px 10px; + padding: var(--ua-space-15) var(--ua-space-25); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-bg); @@ -124,7 +124,7 @@ color: var(--ua-color-text); } .ua-sort-mark { - margin-left: 4px; + margin-left: var(--ua-space-1); font-size: var(--ua-font-size-sm); color: var(--ua-color-accent); } @@ -193,7 +193,7 @@ body.ua-col-resizing { .ua-empty-row td { text-align: center; color: var(--ua-color-text-muted); - padding: 28px 12px; + padding: 28px var(--ua-space-3); } .ua-empty-tip { display: inline-block; @@ -208,7 +208,7 @@ body.ua-col-resizing { } .ua-table-toolbar { display: flex; - gap: 10px; + gap: var(--ua-space-25); align-items: center; margin-bottom: var(--ua-space-3); } @@ -230,7 +230,7 @@ body.ua-col-resizing { /* —— 复选框 / 单选 —— */ .ua-check-row { display: inline-flex; - gap: 6px; + gap: var(--ua-space-15); align-items: center; cursor: pointer; } @@ -248,14 +248,14 @@ body.ua-col-resizing { } .ua-radio-group { display: inline-flex; - gap: 14px; + gap: var(--ua-space-35); flex-wrap: wrap; } /* —— 开关 —— */ .ua-switch { display: inline-flex; - gap: 8px; + gap: var(--ua-space-2); align-items: center; cursor: pointer; } @@ -323,8 +323,8 @@ body.ua-col-resizing { width: 100%; justify-content: space-between; align-items: center; - gap: 8px; - padding: 6px 10px; + gap: var(--ua-space-2); + padding: var(--ua-space-15) var(--ua-space-25); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-bg); @@ -366,7 +366,7 @@ body.ua-col-resizing { right: 0; max-height: 220px; overflow-y: auto; - padding: 4px; + padding: var(--ua-space-1); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-surface); @@ -388,8 +388,8 @@ body.ua-col-resizing { } .ua-select-search { width: 100%; - margin-bottom: 4px; - padding: 6px 10px; + margin-bottom: var(--ua-space-1); + padding: var(--ua-space-15) var(--ua-space-25); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-sm); background: var(--ua-color-bg); @@ -402,13 +402,13 @@ body.ua-col-resizing { box-shadow: var(--ua-focus-ring); } .ua-select-blank { - padding: 8px 10px; + padding: var(--ua-space-2) var(--ua-space-25); font-size: var(--ua-font-size-sm); color: var(--ua-color-text-muted); text-align: center; } .ua-option-group { - padding: 6px 10px 2px; + padding: var(--ua-space-15) var(--ua-space-25) var(--ua-space-05); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); font-weight: 600; @@ -417,15 +417,15 @@ body.ua-col-resizing { user-select: none; } .ua-option-group:not(:first-child) { - margin-top: 4px; + margin-top: var(--ua-space-1); border-top: 1px solid var(--ua-color-border); - padding-top: 8px; + padding-top: var(--ua-space-2); } .ua-option { display: block; width: 100%; text-align: left; - padding: 6px 10px; + padding: var(--ua-space-15) var(--ua-space-25); border: none; border-radius: var(--ua-radius-sm); background: none; @@ -453,7 +453,7 @@ body.ua-col-resizing { display: inline-block; } .ua-menu-trigger { - gap: 6px; + gap: var(--ua-space-15); } .ua-menu-caret { font-size: var(--ua-font-size-xs); @@ -471,8 +471,8 @@ body.ua-col-resizing { min-width: 160px; display: flex; flex-direction: column; - gap: 2px; - padding: 4px; + gap: var(--ua-space-05); + padding: var(--ua-space-1); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-surface); @@ -503,7 +503,7 @@ body.ua-col-resizing { justify-content: space-between; gap: var(--ua-space-4); text-align: left; - padding: 6px 10px; + padding: var(--ua-space-15) var(--ua-space-25); border: none; border-radius: var(--ua-radius-sm); background: none; @@ -541,7 +541,7 @@ body.ua-col-resizing { } .ua-menu-divider { height: 1px; - margin: 4px 2px; + margin: var(--ua-space-1) var(--ua-space-05); background: var(--ua-color-border); } @@ -652,8 +652,8 @@ body.ua-col-resizing { width: 100%; align-items: center; justify-content: space-between; - gap: 8px; - padding: 10px 12px; + gap: var(--ua-space-2); + padding: var(--ua-space-25) var(--ua-space-3); border: none; background: none; color: var(--ua-color-text); @@ -692,12 +692,12 @@ body.ua-col-resizing { } .ua-acc-inner { overflow: hidden; - padding: 0 12px; + padding: 0 var(--ua-space-3); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-md); } .ua-acc-item.ua-open .ua-acc-inner { - padding-bottom: 12px; + padding-bottom: var(--ua-space-3); } /* —— 头像 —— */ @@ -736,8 +736,8 @@ body.ua-col-resizing { .ua-tag { display: inline-flex; align-items: center; - gap: 4px; - padding: 2px 8px; + gap: var(--ua-space-1); + padding: var(--ua-space-05) var(--ua-space-2); border: 1px solid var(--ua-color-border); border-radius: 999px; background: var(--ua-color-surface); @@ -885,7 +885,7 @@ body.ua-col-resizing { .ua-textarea { width: 100%; min-height: 60px; - padding: 6px 10px; + padding: var(--ua-space-15) var(--ua-space-25); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-bg); @@ -898,7 +898,7 @@ body.ua-col-resizing { display: flex; /* 字数统计 + 原有 suffix 并排靠右 */ justify-content: flex-end; align-items: center; - gap: 4px; + gap: var(--ua-space-1); } .ua-textarea-count { color: var(--ua-color-text-muted); @@ -910,10 +910,10 @@ body.ua-col-resizing { .ua-input-number { display: inline-flex; align-items: center; - gap: 4px; + gap: var(--ua-space-1); } .ua-input-number .ua-btn { - padding: 5px 12px; /* 收窄为步进按钮 */ + padding: 5px var(--ua-space-3); /* 收窄为步进按钮 */ line-height: 1.4; } .ua-input-number-field { @@ -1018,7 +1018,7 @@ button.ua-step-dot:hover { .ua-step-text { display: flex; flex-direction: column; - gap: 2px; + gap: var(--ua-space-05); min-width: 0; } .ua-step-label { @@ -1038,7 +1038,7 @@ button.ua-step-dot:hover { /* —— 评分 —— */ .ua-rate { display: inline-flex; - gap: 2px; + gap: var(--ua-space-05); } .ua-rate:focus-visible { outline: 2px solid color-mix(in srgb, var(--ua-color-accent) 45%, transparent); @@ -1149,7 +1149,7 @@ button.ua-step-dot:hover { /* —— 徽标 —— */ .ua-badge { display: inline-block; - padding: 1px 8px; + padding: 1px var(--ua-space-2); border-radius: 999px; font-size: var(--ua-font-size-sm); line-height: 1.6; @@ -1192,7 +1192,7 @@ button.ua-step-dot:hover { align-items: flex-start; gap: var(--ua-space-2); width: 100%; - padding: 8px 12px; + padding: var(--ua-space-2) var(--ua-space-3); transition: opacity var(--ua-duration-base) ease, transform var(--ua-duration-base) ease; border-radius: var(--ua-radius-md); border: 1px solid; @@ -1220,7 +1220,7 @@ button.ua-step-dot:hover { opacity: 0.85; } .ua-alert.is-banner { - padding: 10px 16px; + padding: var(--ua-space-25) var(--ua-space-4); border-width: 0 0 1px; border-radius: 0; } @@ -1230,12 +1230,12 @@ button.ua-step-dot:hover { min-width: 0; } .ua-alert-title { - margin-bottom: 2px; + margin-bottom: var(--ua-space-05); font-weight: 600; } .ua-alert-close { flex: none; - padding: 0 2px; + padding: 0 var(--ua-space-05); border: none; background: none; color: var(--ua-color-text-muted); @@ -1284,7 +1284,7 @@ button.ua-step-dot:hover { .ua-page-jump-wrap { display: inline-flex; /* 跳页输入 + 总页数说明 */ align-items: center; - gap: 4px; + gap: var(--ua-space-1); margin-left: var(--ua-space-2); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); @@ -1298,7 +1298,7 @@ button.ua-step-dot:hover { } .ua-page-jump { width: 48px; - padding: 4px 6px; + padding: var(--ua-space-1) var(--ua-space-15); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-sm); background: var(--ua-color-bg); @@ -1342,7 +1342,7 @@ button.ua-step-dot:hover { } .ua-page-gap { color: var(--ua-color-text-muted); - padding: 0 2px; + padding: 0 var(--ua-space-05); } /* —— 骨架屏:占位灰块 + 扫光 —— */ @@ -1421,7 +1421,7 @@ button.ua-step-dot:hover { pointer-events: auto; } .ua-toast-close { - padding: 0 2px; + padding: 0 var(--ua-space-05); border: none; background: none; color: var(--ua-color-text-muted); @@ -1467,7 +1467,7 @@ button.ua-step-dot:hover { /* —— 进度条:轨道 + 渐变流动填充 —— */ .ua-progress { display: inline-flex; - gap: 10px; + gap: var(--ua-space-25); align-items: center; width: 100%; } @@ -1548,13 +1548,13 @@ button.ua-step-dot:hover { .ua-tabs-head { position: relative; display: inline-flex; - gap: 4px; + gap: var(--ua-space-1); max-width: 100%; overflow-x: auto; border-bottom: 1px solid var(--ua-color-border); } .ua-tab { - padding: 7px 14px; + padding: 7px var(--ua-space-35); border: none; background: none; color: var(--ua-color-text-muted); @@ -1635,13 +1635,13 @@ button.ua-step-dot:hover { .ua-form { display: flex; flex-direction: column; - gap: 14px; + gap: var(--ua-space-35); width: 100%; } .ua-form-item { display: flex; flex-direction: column; - gap: 4px; + gap: var(--ua-space-1); } /* 水平布局:标签左置右对齐 */ .ua-form-h .ua-form-item { @@ -1650,7 +1650,7 @@ button.ua-step-dot:hover { } .ua-form-h .ua-form-label { min-width: 80px; - padding-top: 6px; + padding-top: var(--ua-space-15); text-align: right; } .ua-form-h .ua-form-control { @@ -1669,12 +1669,12 @@ button.ua-step-dot:hover { .ua-form-control { display: flex; flex-direction: column; - gap: 4px; + gap: var(--ua-space-1); } .ua-form-hint { color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); - margin-top: 4px; + margin-top: var(--ua-space-1); } .ua-form-hint[hidden] { display: none; @@ -1699,8 +1699,8 @@ button.ua-step-dot:hover { } .ua-form-actions { display: flex; - gap: 10px; - margin-top: 4px; + gap: var(--ua-space-25); + margin-top: var(--ua-space-1); } /* —— 抽屉(侧滑面板) —— */ @@ -1770,7 +1770,7 @@ button.ua-step-dot:hover { align-items: center; justify-content: space-between; gap: var(--ua-space-2); - padding: 12px 16px; + padding: var(--ua-space-3) var(--ua-space-4); border-bottom: 1px solid var(--ua-color-border); } .ua-drawer-title { @@ -1789,7 +1789,7 @@ button.ua-step-dot:hover { .ua-drawer-body { flex: 1; overflow-y: auto; - padding: 16px; + padding: var(--ua-space-4); color: var(--ua-color-text); font-size: var(--ua-font-size-md); } @@ -1797,7 +1797,7 @@ button.ua-step-dot:hover { display: flex; justify-content: flex-end; gap: var(--ua-space-2); - padding: 12px 16px; + padding: var(--ua-space-3) var(--ua-space-4); border-top: 1px solid var(--ua-color-border); } @@ -1839,7 +1839,7 @@ button.ua-step-dot:hover { display: flex; justify-content: flex-end; gap: var(--ua-space-2); - padding: 12px 16px; + padding: var(--ua-space-3) var(--ua-space-4); border-top: 1px solid var(--ua-color-border); } @keyframes ua-fade { @@ -1857,7 +1857,7 @@ button.ua-step-dot:hover { display: flex; align-items: center; justify-content: space-between; - padding: 12px 16px; + padding: var(--ua-space-3) var(--ua-space-4); border-bottom: 1px solid var(--ua-color-border); } .ua-modal-title { @@ -1874,7 +1874,7 @@ button.ua-step-dot:hover { line-height: 1; } .ua-modal-body { - padding: 16px; + padding: var(--ua-space-4); } .ua-textarea:focus-visible { outline: none; @@ -2089,7 +2089,7 @@ button.ua-step-dot:hover { .ua-input-addon { display: inline-flex; align-items: center; - padding: 6px 10px; + padding: var(--ua-space-15) var(--ua-space-25); color: var(--ua-color-text-muted); background: color-mix(in srgb, var(--ua-color-text) 5%, transparent); font-size: var(--ua-font-size-md); @@ -2098,7 +2098,7 @@ button.ua-step-dot:hover { .ua-input-clear-group { display: inline-flex; /* 清空按钮 / 计数 + 原有 suffix 并排 */ align-items: center; - gap: 4px; + gap: var(--ua-space-1); } .ua-input-count { color: var(--ua-color-text-muted); @@ -2108,7 +2108,7 @@ button.ua-step-dot:hover { .ua-input-clear { border: none; /* 一键清空:小圆钮,弱化视觉 */ background: none; - padding: 0 2px; + padding: 0 var(--ua-space-05); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-md); line-height: 1; @@ -2241,7 +2241,7 @@ button.ua-step-dot:hover { font-variant-numeric: tabular-nums; } .ua-tl-content { - margin: 2px 0 0; + margin: var(--ua-space-05) 0 0; color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); line-height: 1.6; @@ -2286,7 +2286,7 @@ button.ua-step-dot:hover { .ua-seg-btn { position: relative; z-index: 1; - padding: 5px 14px; + padding: 5px var(--ua-space-35); border: 0; border-radius: 999px; background: transparent; @@ -2312,7 +2312,7 @@ button.ua-step-dot:hover { cursor: not-allowed; } .ua-segmented-sm .ua-seg-btn { - padding: 3px 10px; + padding: 3px var(--ua-space-25); font-size: var(--ua-font-size-sm); } .ua-segmented.is-disabled { @@ -2357,7 +2357,7 @@ button.ua-step-dot:hover { } .ua-desc.is-vertical .ua-desc-item { flex-direction: column; - gap: 2px; /* 竖排时字段名与值贴紧,避免看成两行内容 */ + gap: var(--ua-space-05); /* 竖排时字段名与值贴紧,避免看成两行内容 */ } .ua-desc.is-vertical.is-bordered .ua-desc-label { border-right: 0; @@ -2368,7 +2368,7 @@ button.ua-step-dot:hover { display: flex; gap: var(--ua-space-2); min-width: 0; - padding: 4px 0; + padding: var(--ua-space-1) 0; } .ua-desc-label { flex: 0 0 auto; @@ -2397,13 +2397,13 @@ button.ua-step-dot:hover { border-bottom: 1px solid var(--ua-color-border); } .ua-desc.is-bordered .ua-desc-label { - padding: 6px 10px; + padding: var(--ua-space-15) var(--ua-space-25); background: color-mix(in srgb, var(--ua-color-text) 4%, transparent); border-right: 1px solid var(--ua-color-border); white-space: nowrap; } .ua-desc.is-bordered .ua-desc-value { - padding: 6px 10px; + padding: var(--ua-space-15) var(--ua-space-25); flex: 1; } @media (max-width: 640px) { @@ -2416,7 +2416,7 @@ button.ua-step-dot:hover { .ua-stat { display: flex; flex-direction: column; - gap: 2px; + gap: var(--ua-space-05); } .ua-stat-title { color: var(--ua-color-text-muted); @@ -2440,7 +2440,7 @@ button.ua-step-dot:hover { } .ua-stat-trend { font-size: var(--ua-font-size-sm); - margin-left: 2px; + margin-left: var(--ua-space-05); } .ua-stat-trend.is-up { color: var(--ua-color-danger); @@ -2508,7 +2508,7 @@ button.ua-step-dot:hover { min-width: 0; display: flex; flex-direction: column; - gap: 2px; + gap: var(--ua-space-05); } .ua-list-title { font-size: var(--ua-font-size-md); @@ -2610,7 +2610,7 @@ button.ua-step-dot:hover { display: flex; align-items: center; gap: var(--ua-space-1); - padding: 4px var(--ua-space-2); + padding: var(--ua-space-1) var(--ua-space-2); border-radius: var(--ua-radius-sm); color: var(--ua-color-text); cursor: pointer; @@ -2652,7 +2652,7 @@ button.ua-step-dot:hover { gap: var(--ua-space-2); } .ua-tree-search { - padding: 5px 10px; + padding: 5px var(--ua-space-25); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-bg); @@ -2761,7 +2761,7 @@ button.ua-step-dot:hover { color: var(--ua-color-text-muted); cursor: pointer; line-height: 1; - padding: 0 2px; + padding: 0 var(--ua-space-05); } .ua-upload-remove:hover { color: var(--ua-color-danger); @@ -2830,7 +2830,7 @@ button.ua-step-dot:hover { color: var(--ua-color-text-muted); cursor: pointer; line-height: 1; - padding: 0 2px; + padding: 0 var(--ua-space-05); } .ua-notice-close:hover { color: var(--ua-color-text); @@ -2924,7 +2924,7 @@ button.ua-step-dot:hover { } .ua-transfer-search { margin: var(--ua-space-1) var(--ua-space-2) 0; - padding: 4px var(--ua-space-1); + padding: var(--ua-space-1) var(--ua-space-1); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: var(--ua-color-bg); @@ -2940,7 +2940,7 @@ button.ua-step-dot:hover { .ua-transfer-list { display: flex; flex-direction: column; - gap: 2px; + gap: var(--ua-space-05); height: 220px; margin: 0; padding: var(--ua-space-1); @@ -2967,7 +2967,7 @@ button.ua-step-dot:hover { display: flex; align-items: center; gap: var(--ua-space-1); - padding: 6px var(--ua-space-1); + padding: var(--ua-space-15) var(--ua-space-1); cursor: pointer; } .ua-transfer-item.is-disabled .ua-transfer-label { @@ -2980,7 +2980,7 @@ button.ua-step-dot:hover { color: var(--ua-color-text-muted); } .ua-transfer-foot { - padding: 6px var(--ua-space-2); + padding: var(--ua-space-15) var(--ua-space-2); border-top: 1px solid var(--ua-color-border); font-size: var(--ua-font-size-sm); color: var(--ua-color-text-muted); @@ -3012,7 +3012,7 @@ button.ua-step-dot:hover { justify-content: space-between; gap: var(--ua-space-1); width: 100%; - padding: 8px var(--ua-space-2); + padding: var(--ua-space-2) var(--ua-space-2); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: var(--ua-color-surface); @@ -3047,7 +3047,7 @@ button.ua-step-dot:hover { position: absolute; z-index: 40; display: none; - margin-top: 6px; + margin-top: var(--ua-space-15); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-surface); @@ -3060,7 +3060,7 @@ button.ua-step-dot:hover { } .ua-cascader-pop.is-flip { margin-top: 0; - margin-bottom: 6px; + margin-bottom: var(--ua-space-15); } .ua-cascader-panels { display: flex; @@ -3069,7 +3069,7 @@ button.ua-step-dot:hover { .ua-cascader-col { display: flex; flex-direction: column; - gap: 2px; + gap: var(--ua-space-05); min-width: 132px; max-height: 220px; margin: 0; @@ -3086,7 +3086,7 @@ button.ua-step-dot:hover { align-items: center; justify-content: space-between; gap: var(--ua-space-1); - padding: 6px var(--ua-space-1); + padding: var(--ua-space-15) var(--ua-space-1); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: transparent; @@ -3184,7 +3184,7 @@ button.ua-step-dot:hover { } .ua-autocomplete-input { width: 100%; - padding: 8px var(--ua-space-2); + padding: var(--ua-space-2) var(--ua-space-2); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: var(--ua-color-surface); @@ -3207,7 +3207,7 @@ button.ua-step-dot:hover { left: 0; right: 0; display: none; - margin-top: 4px; + margin-top: var(--ua-space-1); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-surface); @@ -3220,7 +3220,7 @@ button.ua-step-dot:hover { } .ua-autocomplete-pop.is-flip { margin-top: 0; - margin-bottom: 4px; + margin-bottom: var(--ua-space-1); } .ua-autocomplete-list { max-height: 220px; @@ -3234,7 +3234,7 @@ button.ua-step-dot:hover { align-items: center; justify-content: space-between; gap: var(--ua-space-1); - padding: 6px var(--ua-space-1); + padding: var(--ua-space-15) var(--ua-space-1); border-radius: var(--ua-radius-sm); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); @@ -3275,7 +3275,7 @@ button.ua-step-dot:hover { justify-content: space-between; gap: var(--ua-space-1); width: 100%; - padding: 8px var(--ua-space-2); + padding: var(--ua-space-2) var(--ua-space-2); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: var(--ua-color-surface); @@ -3307,7 +3307,7 @@ button.ua-step-dot:hover { z-index: 40; display: none; width: 264px; - margin-top: 6px; + margin-top: var(--ua-space-15); padding: var(--ua-space-2); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); @@ -3320,7 +3320,7 @@ button.ua-step-dot:hover { } .ua-datepicker-pop.is-flip { margin-top: 0; - margin-bottom: 6px; + margin-bottom: var(--ua-space-15); } .ua-datepicker-head { display: flex; @@ -3360,8 +3360,8 @@ button.ua-step-dot:hover { .ua-datepicker-grid { display: grid; grid-template-columns: repeat(7, 1fr); - gap: 2px; - margin-top: 2px; + gap: var(--ua-space-05); + margin-top: var(--ua-space-05); } .ua-datepicker-blank { height: 28px; @@ -3401,7 +3401,7 @@ button.ua-step-dot:hover { margin-top: var(--ua-space-1); } .ua-datepicker-today { - padding: 4px var(--ua-space-1); + padding: var(--ua-space-1) var(--ua-space-1); font-size: var(--ua-font-size-sm); color: var(--ua-color-accent); background: transparent; @@ -3417,7 +3417,7 @@ button.ua-step-dot:hover { border-bottom: 1px solid var(--ua-color-border); } .ua-datepicker-preset { - padding: 2px 8px; + padding: var(--ua-space-05) var(--ua-space-2); font-size: var(--ua-font-size-sm); color: var(--ua-color-text-muted); background: transparent; @@ -3503,7 +3503,7 @@ button.ua-step-dot:hover { align-items: center; gap: var(--ua-space-1); width: 100%; - padding: 6px var(--ua-space-2); + padding: var(--ua-space-15) var(--ua-space-2); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: var(--ua-color-surface); @@ -3550,7 +3550,7 @@ button.ua-step-dot:hover { z-index: 40; display: none; width: 232px; - margin-top: 6px; + margin-top: var(--ua-space-15); padding: var(--ua-space-2); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); @@ -3563,7 +3563,7 @@ button.ua-step-dot:hover { } .ua-colorpicker-pop.is-flip { margin-top: 0; - margin-bottom: 6px; + margin-bottom: var(--ua-space-15); } .ua-colorpicker-grid { display: grid; @@ -3602,7 +3602,7 @@ button.ua-step-dot:hover { .ua-colorpicker-hex { flex: 1; min-width: 0; - padding: 4px var(--ua-space-1); + padding: var(--ua-space-1) var(--ua-space-1); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: var(--ua-color-bg); @@ -3661,7 +3661,7 @@ button.ua-step-dot:hover { color: var(--ua-color-text); } .ua-tour-close { - padding: 0 4px; + padding: 0 var(--ua-space-1); font-size: var(--ua-font-size-lg); line-height: 1; color: var(--ua-color-text-muted); @@ -3673,7 +3673,7 @@ button.ua-step-dot:hover { color: var(--ua-color-text); } .ua-tour-desc { - margin-top: 4px; + margin-top: var(--ua-space-1); font-size: var(--ua-font-size-sm); line-height: 1.6; color: var(--ua-color-text-muted); @@ -3747,7 +3747,7 @@ button.ua-step-dot:hover { align-items: center; gap: var(--ua-space-1); min-height: 36px; - padding: 4px var(--ua-space-1); + padding: var(--ua-space-1) var(--ua-space-1); background: var(--ua-color-surface); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-sm); @@ -3764,9 +3764,9 @@ button.ua-step-dot:hover { .ua-multiselect-tag { display: inline-flex; align-items: center; - gap: 4px; + gap: var(--ua-space-1); max-width: 160px; - padding: 2px 6px; + padding: var(--ua-space-05) var(--ua-space-15); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: color-mix(in srgb, var(--ua-color-accent) 10%, transparent); @@ -3802,7 +3802,7 @@ button.ua-step-dot:hover { .ua-multiselect-input { flex: 1; min-width: 70px; - padding: 2px 0; + padding: var(--ua-space-05) 0; font-size: var(--ua-font-size-sm); color: var(--ua-color-text); background: transparent; @@ -3815,7 +3815,7 @@ button.ua-step-dot:hover { left: 0; right: 0; display: none; - margin-top: 4px; + margin-top: var(--ua-space-1); border: 1px solid var(--ua-color-border); border-radius: var(--ua-radius-md); background: var(--ua-color-surface); @@ -3828,7 +3828,7 @@ button.ua-step-dot:hover { } .ua-multiselect-pop.is-flip { margin-top: 0; - margin-bottom: 4px; + margin-bottom: var(--ua-space-1); } .ua-multiselect-list { max-height: 220px; @@ -3838,7 +3838,7 @@ button.ua-step-dot:hover { list-style: none; } .ua-multiselect-group { - padding: 6px var(--ua-space-1) 2px; + padding: var(--ua-space-15) var(--ua-space-1) var(--ua-space-05); color: var(--ua-color-text-muted); font-size: var(--ua-font-size-sm); font-weight: 600; @@ -3849,7 +3849,7 @@ button.ua-step-dot:hover { display: flex; align-items: center; gap: var(--ua-space-1); - padding: 6px var(--ua-space-1); + padding: var(--ua-space-15) var(--ua-space-1); font-size: var(--ua-font-size-sm); color: var(--ua-color-text); border-radius: var(--ua-radius-sm); @@ -3889,7 +3889,7 @@ button.ua-step-dot:hover { .ua-multiselect-opt { transition: none; } } .ua-tree-row .ua-tree-box { - margin-right: 2px; + margin-right: var(--ua-space-05); } @media (prefers-reduced-motion: reduce) { .ua-card-hoverable { @@ -3917,7 +3917,7 @@ button.ua-step-dot:hover { position: relative; display: flex; flex-direction: column; - gap: 2px; + gap: var(--ua-space-05); padding-left: var(--ua-space-3); } .ua-anchor::before { @@ -4042,7 +4042,7 @@ button.ua-step-dot:hover { /* —— 面包屑折叠按钮 —— */ .ua-crumb-fold { - padding: 0 2px; + padding: 0 var(--ua-space-05); border: none; background: none; color: var(--ua-color-text-muted); @@ -4125,7 +4125,7 @@ button.ua-step-dot:hover { .ua-calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); - gap: 2px; + gap: var(--ua-space-05); } .ua-calendar-week { margin-bottom: var(--ua-space-1); @@ -4138,7 +4138,7 @@ button.ua-step-dot:hover { flex-direction: column; align-items: center; gap: 1px; - padding: 4px 0; + padding: var(--ua-space-1) 0; border: none; border-radius: var(--ua-radius-sm); background: none; @@ -4219,7 +4219,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } .ua-type.is-mark { background: color-mix(in srgb, var(--ua-color-warning) 24%, transparent); border-radius: var(--ua-radius-sm); - padding: 0 2px; + padding: 0 var(--ua-space-05); } .ua-type.is-code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; @@ -4249,7 +4249,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } color: var(--ua-color-text-muted); font: inherit; font-size: var(--ua-font-size-sm); - padding: 0 2px; + padding: 0 var(--ua-space-05); border-radius: var(--ua-radius-sm); cursor: pointer; transition: color var(--ua-duration-fast) ease, background var(--ua-duration-fast) ease; @@ -4362,7 +4362,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } right: -4px; min-width: 18px; height: 18px; - padding: 0 4px; + padding: 0 var(--ua-space-1); border-radius: 999px; background: var(--ua-color-danger); color: var(--ua-color-accent-contrast); @@ -4542,7 +4542,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } .ua-link { display: inline-flex; align-items: baseline; - gap: 2px; + gap: var(--ua-space-05); color: var(--ua-color-accent); text-decoration: none; text-underline-offset: 2px; @@ -4679,7 +4679,7 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); } display: flex; flex: 1; flex-direction: column; - gap: 2px; + gap: var(--ua-space-05); } .ua-slider-marks { position: relative; diff --git a/packages/tools/cli/lib/visual-rules.mjs b/packages/tools/cli/lib/visual-rules.mjs index f456953..505c640 100644 --- a/packages/tools/cli/lib/visual-rules.mjs +++ b/packages/tools/cli/lib/visual-rules.mjs @@ -36,6 +36,11 @@ export const KIND_META = [ label: '焦点环变体', hint: '自绘 0 0 0 Npx 色环,应改用 --ua-focus-ring / --ua-focus-ring-inset', }, + { + key: 'space-literal', + label: '内距字面值', + hint: 'padding/gap/margin 写死 px,应改用 --ua-space-*', + }, ]; /*** 作用:需要扫描的 CSS 属性白名单(其余属性本轮不动;用白名单顺带避开伪类选择器误判) ***/ @@ -48,6 +53,48 @@ const SCAN_PROPS = new Set([ 'box-shadow', 'border-radius', 'outline', + 'padding', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left', + 'margin', + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left', + 'gap', + 'row-gap', + 'column-gap', +]); + +/*** 作用:内距类属性集合(迁移与统计共用同一判定) ***/ +const SPACE_PROPS = new Set([ + 'padding', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left', + 'margin', + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left', + 'gap', + 'row-gap', + 'column-gap', +]); + +/*** 作用:内距 px 字面值 → 令牌(半步档位 05/15/25/35 与既有 1..4 组成完整梯子;1px 是边框语义不入表) ***/ +const SPACE_TOKENS = new Map([ + ['2px', 'var(--ua-space-05)'], + ['4px', 'var(--ua-space-1)'], + ['6px', 'var(--ua-space-15)'], + ['8px', 'var(--ua-space-2)'], + ['10px', 'var(--ua-space-25)'], + ['12px', 'var(--ua-space-3)'], + ['14px', 'var(--ua-space-35)'], + ['16px', 'var(--ua-space-4)'], ]); /*** 作用:字号字面值 → 令牌(10/11 归并到 xs,避免 11px 单独成档) ***/ @@ -114,7 +161,7 @@ export function blankComments(src) { /*** 作用:逐条产出「属性 + 值」及其在原文中的偏移;只认白名单属性且必须以分号收尾(避开伪类选择器) ***/ export function eachDeclaration(code) { - const re = /(transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline)\s*:\s*([^;{}]*);/gi; + const re = /(padding-top|padding-right|padding-bottom|padding-left|padding|margin-top|margin-right|margin-bottom|margin-left|margin|row-gap|column-gap|gap|transition-duration|animation-duration|transition|animation|font-size|box-shadow|border-radius|outline)\s*:\s*([^;{}]*);/gi; const list = []; for (const m of code.matchAll(re)) { const prop = m[1].toLowerCase(); @@ -205,6 +252,20 @@ function migrateShadow(core) { return { kind: 'shadow-literal', value: `var(--ua-shadow-${hit})` }; } +/*** 作用:迁移单条内距声明:逐个 px 记号查 SPACE_TOKENS 替换(负值与 calc 内不算,返回 null 表示无需改动) ***/ +function migrateSpace(core) { + let hits = 0; + const next = String(core).replace(/(^|[\s(,])(-?\d+(?:\.\d+)?)px\b/gi, (full, lead, num) => { + if (String(num).startsWith('-')) return full; // 负外距是偏移语义,不入令牌 + const mapped = SPACE_TOKENS.get(`${num}px`); + if (!mapped) return full; // 表外取值(3px / 5px / 22px 等)留给后续条目评估 + hits++; + return `${lead}${mapped}`; + }); + if (!hits) return null; + return { kind: 'space-literal', value: next }; +} + /*** 作用:判定单条声明是否需要迁移,返回 { kind, value } 或 null(幂等:已是令牌就返回 null) ***/ export function migrateDeclaration(prop, rawValue) { const { core, tail } = splitImportant(rawValue); @@ -219,6 +280,8 @@ export function migrateDeclaration(prop, rawValue) { result = migrateMotion(prop, core); } else if (prop === 'box-shadow') { result = migrateShadow(core); + } else if (SPACE_PROPS.has(prop)) { + result = migrateSpace(core); } if (!result) return null; return { kind: result.kind, value: result.value + tail }; @@ -259,6 +322,12 @@ function detect(prop, rawValue, key) { if (!/^(inset )?0 0 0 [2-8]px \S/.test(value)) return 0; return /var\(--ua-color-(accent|warning|danger)/.test(value) ? 1 : 0; } + if (key === 'space-literal') { + if (!SPACE_PROPS.has(prop)) return 0; + // 口径:数非负、非 1px 的 px 记号(1px 是边框/分隔线语义,负外距是偏移语义,都不算漂移) + const tokens = String(value).match(/(^|[\s(,])(\d+(?:\.\d+)?)px\b/gi) ?? []; + return tokens.filter((t) => !/(^|[\s(,])1px$/i.test(t)).length; + } return 0; }