🎨 style(transaction): 手动事务提交按钮改用 warn 独立配色并修复待提交条数看不见

- 手动事务的「提交」按钮不再跟随主题强调色:它是需要用户显式决断的状态,与普通主操作同色时
  无法区分。改用 warn 实心填充,与已是 danger 的「回滚」形成语义配对。
  原先是 accent-soft 底 + accent-2 文字,同色系淡色叠淡色,文字本身对比就很低
- 修复待提交条数徽标的数字看不见:底色原为硬编码 rgba(22,163,74,.18)、文字为 --gn-accent-2,
  而按钮底是 --gn-accent —— 三者几乎同色,实测对比度仅 1.10–1.17,
  静止状态数字完全不可见,只有 hover 把按钮压暗后才显现(DataGrid 与 SQL 编辑器两处同病)
- 新增 --gn-on-warn / --gn-warn-hover / --gn-warn-active / --gn-warn-badge-bg /
  --gn-accent-badge-bg。前景色按亮度择优,因为 warn 跨度很大(#9f5f1d ~ #ebcb8b);
  徽标底色的叠加方向也随前景色反向偏移,写死半透明白会让浅色主题的白字只剩 2.65
- 补 3 项不变式测试:warn 标签与徽标数字对比度均 ≥4.5、warn 三态逐级加深且互不相同。
  该用例已实测抓住浅色主题 warm-paper 的徽标对比不足

实测:徽标数字对比度由 1.10–1.17 提升到 ≥4.5;warn 标签 7.59–11.80。
This commit is contained in:
Syngnat
2026-07-26 21:51:08 +08:00
parent 5e1afad49f
commit 57d6e50884
4 changed files with 146 additions and 15 deletions

View File

@@ -262,38 +262,50 @@ body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button {
display: inline-flex !important;
align-items: center;
gap: 6px;
border-color: transparent !important;
background: var(--gn-accent-soft) !important;
color: var(--gn-accent-2) !important;
/* 待提交事务用 warn 实心填充,刻意不跟随主题强调色:
它是需要用户显式决断的状态,与普通主操作同色时无法区分(回滚用 danger二者语义配对
原先是 accent-soft 底 + accent-2 文字,两者都是同一色系的淡色,实测文字对比极低。 */
border-color: var(--gn-warn) !important;
background: var(--gn-warn) !important;
color: var(--gn-on-warn, #17130a) !important;
font-weight: 750 !important;
transition: background-color 120ms ease, box-shadow 120ms ease, border-color 120ms ease !important;
}
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:hover,
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:focus,
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:focus-visible,
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:active {
border-color: rgba(22, 163, 74, 0.18) !important;
background: color-mix(in srgb, var(--gn-accent-soft) 80%, rgba(22, 163, 74, 0.28)) !important;
color: var(--gn-accent-2) !important;
box-shadow: 0 0 0 1px rgba(22, 163, 74, 0.12), 0 6px 14px rgba(22, 163, 74, 0.12) !important;
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:focus-visible {
border-color: var(--gn-warn-hover, var(--gn-warn)) !important;
background: var(--gn-warn-hover, var(--gn-warn)) !important;
color: var(--gn-on-warn, #17130a) !important;
box-shadow: var(--gn-shadow-sm), inset 0 0.5px 0 rgba(255, 255, 255, 0.15) !important;
}
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:active {
border-color: var(--gn-warn-active, var(--gn-warn)) !important;
background: var(--gn-warn-active, var(--gn-warn)) !important;
color: var(--gn-on-warn, #17130a) !important;
box-shadow: none !important;
}
/* 待提交条数徽标:底色用白色半透明叠加而非硬编码绿色,文字用 on-warn。
原先底是 rgba(22,163,74,.18)、文字是 --gn-accent-2与按钮底色几乎同色
(实测对比度仅 1.101.17),静止状态下数字完全看不见,只有 hover 变暗后才显现。 */
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button .gn-v2-toolbar-kbd {
margin-left: 0;
min-width: 18px;
justify-content: center;
border: none;
background: rgba(22, 163, 74, 0.18);
color: var(--gn-accent-2);
background: var(--gn-warn-badge-bg, rgba(255, 255, 255, 0.35));
color: var(--gn-on-warn, #17130a);
}
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:hover .gn-v2-toolbar-kbd,
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:focus .gn-v2-toolbar-kbd,
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:focus-visible .gn-v2-toolbar-kbd,
body[data-ui-version="v2"] .gn-v2-query-transaction-commit-button:active .gn-v2-toolbar-kbd {
background: rgba(22, 163, 74, 0.24);
color: var(--gn-accent-2);
background: var(--gn-warn-badge-bg, rgba(255, 255, 255, 0.42));
color: var(--gn-on-warn, #17130a);
}
body[data-ui-version="v2"] .gn-v2-query-toolbar-icon-action.ant-btn,

View File

@@ -105,6 +105,60 @@ const parseHexColor = (value: string): [number, number, number] | null => {
];
};
const channelLuminance = (channel: number): number => {
const c = channel / 255;
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
};
/**
* readableOnColor 按底色亮度挑选可读的前景色。
*
* warn 在各预设间跨度很大(浅色主题为 #9f5f1d 这类深琥珀,深色主题为 #ebcb8b 这类浅琥珀),
* 固定用深色或白色都会在另一端失效,因此按相对亮度择优。
*/
const DARK_ON_COLOR = '#17130a';
const readableOnColor = (background: string): string => {
const rgb = parseHexColor(background);
if (!rgb) {
return '#ffffff';
}
const relativeLuminance = (channels: [number, number, number]): number => (
0.2126 * channelLuminance(channels[0])
+ 0.7152 * channelLuminance(channels[1])
+ 0.0722 * channelLuminance(channels[2])
);
const contrast = (a: number, b: number): number => {
const [hi, lo] = a >= b ? [a, b] : [b, a];
return (hi + 0.05) / (lo + 0.05);
};
const backgroundLuminance = relativeLuminance(rgb);
const darkRgb = parseHexColor(DARK_ON_COLOR);
const darkContrast = darkRgb
? contrast(backgroundLuminance, relativeLuminance(darkRgb))
: 1;
const whiteContrast = contrast(backgroundLuminance, 1);
return darkContrast >= whiteContrast ? DARK_ON_COLOR : '#ffffff';
};
/**
* badgeBackground 计算「按钮内计数徽标」的底色。
*
* 叠加方向必须跟随前景色,不能写死成半透明白:
* warn 在浅色主题下是深琥珀(#9f5f1d前景色随之取白此时若把徽标底往白色叠加
* 白字只剩 2.65 的对比度;反之深色主题的浅琥珀底配深字,则必须往白色叠加。
* 因此按前景色是深还是浅,决定徽标底往相反方向偏移。
*/
const badgeBackground = (base: string, onColor: string): string => {
const rgb = parseHexColor(onColor);
// 前景偏深时把徽标底往白色偏移,前景偏浅(白字)时往黑色偏移。
const onIsDark = rgb
? 0.2126 * channelLuminance(rgb[0]) + 0.7152 * channelLuminance(rgb[1]) + 0.0722 * channelLuminance(rgb[2]) < 0.5
: true;
return mixHex(base, onIsDark ? '#ffffff' : '#000000', 0.65);
};
/** mixHex 按 ratio 保留 accent、其余混入 towards返回十六进制。 */
const mixHex = (accent: string, towards: string, ratio: number): string => {
const a = parseHexColor(accent);
@@ -177,6 +231,15 @@ body[data-custom-theme][data-ui-version="v2"] {
--gn-accent-hover: ${mixHex(palette.accent, accentStateAnchor(palette), ACCENT_HOVER_RATIO)};
--gn-accent-active: ${mixHex(palette.accent, accentStateAnchor(palette), ACCENT_ACTIVE_RATIO)};
/* 手动事务的「提交」按钮刻意不跟随主题强调色:待提交事务是需要用户显式决断的状态,
若与普通主操作同色则无法区分(回滚已用 danger提交用 warn 形成语义配对)。
on-warn 按亮度择优,因为 warn 在各预设间跨度很大(#9f5f1d ~ #ebcb8b。 */
--gn-on-warn: ${readableOnColor(palette.warn)};
--gn-warn-hover: ${mixHex(palette.warn, accentStateAnchor(palette), ACCENT_HOVER_RATIO)};
--gn-warn-active: ${mixHex(palette.warn, accentStateAnchor(palette), ACCENT_ACTIVE_RATIO)};
--gn-warn-badge-bg: ${badgeBackground(palette.warn, readableOnColor(palette.warn))};
--gn-accent-badge-bg: ${badgeBackground(palette.accent, palette.onAccent)};
--gn-ant-primary: ${palette.accent};
--gn-ant-primary-hover: ${mixHex(palette.accent, accentStateAnchor(palette), ACCENT_HOVER_RATIO)};
--gn-ant-primary-active: ${mixHex(palette.accent, accentStateAnchor(palette), ACCENT_ACTIVE_RATIO)};

View File

@@ -102,3 +102,48 @@ describe('内置主题的主要操作按钮三态', () => {
}
});
});
/**
* 手动事务「提交」按钮用 warn 实心填充,刻意不跟随主题强调色。
* 回归背景:原先用 accent-soft 底 + accent-2 文字,与普通主操作同色系且文字对比极低;
* 待提交条数徽标更是用硬编码绿底 + accent-2 文字,实测对比度仅 1.101.17
* 静止状态下数字完全看不见,只有 hover 把按钮压暗后才显现。
*/
describe('手动事务提交按钮的 warn 配色', () => {
it('warn 前景色在每个主题下都达到正文可读级≥4.5', () => {
for (const preset of BUILTIN_CUSTOM_THEME_PRESETS) {
const warn = readToken(preset.css, '--gn-warn');
const onWarn = readToken(preset.css, '--gn-on-warn');
expect(
contrastRatio(onWarn, warn),
`${preset.id} 的 warn 标签对比度不足`,
).toBeGreaterThanOrEqual(4.5);
}
});
it('待提交条数徽标的数字在静止状态即可读≥4.5', () => {
for (const preset of BUILTIN_CUSTOM_THEME_PRESETS) {
const onWarn = readToken(preset.css, '--gn-on-warn');
const badgeHex = readToken(preset.css, '--gn-warn-badge-bg');
expect(
contrastRatio(onWarn, badgeHex),
`${preset.id} 的徽标数字对比度不足`,
).toBeGreaterThanOrEqual(4.5);
}
});
it('warn 的三态同样逐级加深且互不相同', () => {
for (const preset of BUILTIN_CUSTOM_THEME_PRESETS) {
const warn = readToken(preset.css, '--gn-warn');
const hover = readToken(preset.css, '--gn-warn-hover');
const active = readToken(preset.css, '--gn-warn-active');
expect(hover, `${preset.id} 的 warn hover 不应等于基色`).not.toBe(warn);
expect(active, `${preset.id} 的 warn active 不应等于 hover`).not.toBe(hover);
expect(
contrastRatio(warn, hover),
`${preset.id} 的 warn base→hover 对比度不足`,
).toBeGreaterThanOrEqual(MIN_STATE_DELTA);
}
});
});

View File

@@ -4091,10 +4091,21 @@ body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-commit-button:disabled {
box-shadow: none !important;
}
/* 待提交条数徽标:底色改用白色半透明叠加、文字改用 on-accent。
原先底是硬编码的 rgba(22,163,74,.18)、文字是 --gn-accent-2
而按钮底色是 --gn-accent —— 三者几乎同色(实测对比度仅 1.101.17
静止状态下数字完全看不见,只有 hover 把按钮压暗后才显现。 */
body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-commit-button .gn-v2-toolbar-kbd {
border: none;
background: rgba(22, 163, 74, 0.18);
color: var(--gn-accent-2);
background: var(--gn-accent-badge-bg, rgba(255, 255, 255, 0.35));
color: var(--gn-on-accent, #17130a);
}
body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-commit-button:hover .gn-v2-toolbar-kbd,
body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-commit-button:focus-visible .gn-v2-toolbar-kbd,
body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-commit-button:active .gn-v2-toolbar-kbd {
background: var(--gn-accent-badge-bg, rgba(255, 255, 255, 0.42));
color: var(--gn-on-accent, #17130a);
}
body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-ai-insight-button {