task-4(壳层/路由): 定义深色侧边栏与浅色内容区设计 token 并锁定不变量

新增 src/styles/theme.ts 语义 token 单源 + 相对亮度/对比度/isDarkSurface,
tests 校验侧边栏暗、内容区亮、正文 WCAG AA、与 main.css 变量交叉一致防漂移。
This commit is contained in:
2026-09-05 12:15:10 +08:00
parent 7065dbb53e
commit af68b20f87
2 changed files with 144 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
/**
* Admin 主题 token 单源(任务 4):深色侧边栏 + 浅色内容区。
* main.css 使用同名 --admin-* CSS 变量承载渲染值;tests 用本模块校验
* “侧边栏为暗、内容区为亮、正文对比度达标”的不变量,防止误改成浅色侧栏。
*/
export const ADMIN_THEME = {
sidebar: '#192132',
sidebarDeep: '#121827',
primary: '#6366f1',
primarySoft: '#eef0ff',
bg: '#f3f5f9',
border: '#e5e7ef',
text: '#1f2937',
muted: '#7b8495',
} as const
export type AdminThemeKey = keyof typeof ADMIN_THEME
export const ADMIN_THEME_KEYS = Object.keys(ADMIN_THEME) as AdminThemeKey[]
/** CSS 变量名:camelCase -> kebabsidebarDeep -> --admin-sidebar-deep)。 */
export function cssVarName(key: AdminThemeKey): string {
const kebab = key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)
return `--admin-${kebab}`
}
function parseHex(hex: string): { r: number; g: number; b: number } {
if (!/^#[0-9a-fA-F]{6}$/.test(hex)) {
throw new Error(`主题色必须是 #rrggbb 六位十六进制: ${hex}`)
}
const raw = hex.slice(1)
return {
r: parseInt(raw.slice(0, 2), 16),
g: parseInt(raw.slice(2, 4), 16),
b: parseInt(raw.slice(4, 6), 16),
}
}
/** sRGB 通道 -> 线性亮度。 */
function channelLuminance(channel: number): number {
const s = channel / 255
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
}
/** 相对亮度(0 纯黑 ~ 1 纯白)。 */
export function relativeLuminance(hex: string): number {
const { r, g, b } = parseHex(hex)
return 0.2126 * channelLuminance(r) + 0.7152 * channelLuminance(g) + 0.0722 * channelLuminance(b)
}
export function isDarkSurface(hex: string, threshold = 0.2): boolean {
return relativeLuminance(hex) < threshold
}
/** WCAG 对比度(1 ~ 21),fg/bg 可为 8 位 hex。 */
export function contrastRatio(fgHex: string, bgHex: string): number {
const lighter = Math.max(relativeLuminance(fgHex), relativeLuminance(bgHex))
const darker = Math.min(relativeLuminance(fgHex), relativeLuminance(bgHex))
return (lighter + 0.05) / (darker + 0.05)
}
/** 取某个语义 token 的 hex;未知 key 给出可操作错误。 */
export function tokenHex(key: AdminThemeKey): string {
const value = ADMIN_THEME[key]
if (!value) {
throw new Error(`未知 Admin 主题 token: ${String(key)},允许值: ${ADMIN_THEME_KEYS.join(', ')}`)
}
return value
}