task-4(壳层/路由): 定义深色侧边栏与浅色内容区设计 token 并锁定不变量
新增 src/styles/theme.ts 语义 token 单源 + 相对亮度/对比度/isDarkSurface, tests 校验侧边栏暗、内容区亮、正文 WCAG AA、与 main.css 变量交叉一致防漂移。
This commit is contained in:
@@ -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 -> kebab(sidebarDeep -> --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
|
||||
}
|
||||
Reference in New Issue
Block a user