fix(webui): 导航改"按主题的玻璃"(浅色白玻璃+深字)+ 去掉整屏闪的入场动画
用户两句话:
「那你为什么不把白字换成黑字或者自动反色或者描边呢?」
「部分动画十分不合理,会导致页面大范围的闪动,且不能让人自然的把注意力集中在
将要出现的页面上」
## ① 导航:他说得对,我之前是用错误的方式解决对比度
这个应用是**浅色底 + 深字**,导航却是全页唯一一块黑的 —— 那才是"割裂"。我上一轮
为了"白字对比度"把它做成深玻璃,等于**把一块地方永久压黑**来回避问题。
现在导航底色/文字全部走令牌,按主题切换:
浅色:白玻璃 rgb(255 255 255 / .72) + 深字 #475569 → 对比度 7.48:1
深色:深玻璃 rgb(15 23 42 / .72) + 亮字 #94a3b8 → 自动反色
组件侧改成 `.nav-rail` / `.nav-item[data-active]` 令牌类(Sidebar 与 NarrowNav 同一套)。
同时删掉壁纸模式里写死的深玻璃规则 —— 那条正是"为什么还是黑色"。
## ② 动画:整面板入场删掉
先是从"所有面板一起动"改成"只动主内容区",试下来仍然不对:页面级淡入会把
**已经在那儿的框架**也一起暗一下,观感还是闪。所以这一档整体删掉,只保留局部、
有明确语义的动效(日历翻页、菜单展开)。实测切视图时 `animatedOnSwitch = 0`。
骨架不动,注意力自然落在变化的那块内容上。
## ③ 一条我自己的误判(值得记下)
深色主题下我量到导航文字对比度只有 2.36,一度以为是变量/级联的问题,还写死了一份
深色字面值。**那是误判**:`.nav-item` 有 `transition: color .15s`,我在切主题后
**立刻**读 computed color,拿到的是过渡的**起点**(浅色值)。决定性证据是连内联
`style.color` 都"改不动"它 —— 级联不可能这样,只可能是还在过渡中。等 400ms 再量就正常。
写死的那份已撤掉,并在 CSS 里留下说明;新判据 `test/manual/nav-contrast-verify.mjs`
用 WCAG 比值量对比度(不是"颜色是不是黑的"),每次读之前等 400ms。
背景套件 32 条全绿(含"导航走令牌 + 两套令牌都在")。
This commit is contained in:
@ -73,8 +73,17 @@ for (const f of ['BackButton', 'NarrowOnly']) {
|
||||
}
|
||||
|
||||
// 5) 底部导航要避开 iPhone 手势条
|
||||
//
|
||||
// 注意查的位置:2026-09-14 底部导航改成「悬浮玻璃条」后,贴底通栏的写法
|
||||
// (组件里 padding-bottom)换成了 index.css 里 .narrow-nav 的下外边距。
|
||||
// 断言只看组件文件就恒为假 —— 这是一个**过期断言**,会让整份套件一直红着,
|
||||
// 而红着的套件挡不住真回归。所以两处都认。
|
||||
const nav = read('../src/components/NarrowNav.tsx');
|
||||
check('底部导航留了安全区内边距', nav.includes('safe-area-inset-bottom'));
|
||||
check(
|
||||
'底部导航留了安全区内边距(组件或 .narrow-nav 样式)',
|
||||
nav.includes('safe-area-inset-bottom') ||
|
||||
/\.narrow-nav[\s\S]{0,400}safe-area-inset-bottom/.test(read('../src/index.css'))
|
||||
);
|
||||
|
||||
// 5.1) 抽屉式侧栏已删。
|
||||
// 它装的六项与底部导航完全重复,唯一独有的是退出登录;代价是 z-50 的
|
||||
@ -181,5 +190,49 @@ for (const f of wide) {
|
||||
check(`${f} 没有裸 px-6(应为 px-4 md:px-6)`, bare.length === 0, `发现 ${bare.length} 处`);
|
||||
}
|
||||
|
||||
// 7) 窄屏阅读:顶部只留标题、底部收起为悬浮球
|
||||
//
|
||||
// 用户(2026-09-14):「窄屏页面阅读邮件时,顶部的邮件信息和底部的输入框等
|
||||
// 占用了绝大部分页面,用户只能通过中间的一小块看邮件……上面缩为窄栏,只显示
|
||||
// 邮件标题,点击展开显示完整信息,底部一个悬浮的聊天图标的球,点一下展开输入框」。
|
||||
//
|
||||
// 这两处坏掉的形态很隐蔽:组件照样渲染、宽屏一切正常,只是窄屏可读区被挤成一条缝。
|
||||
// 所以在结构层钉住「窄屏默认收起 + 有展开出口 + 有收回归口」。
|
||||
const mv = read('../src/components/MailView.tsx');
|
||||
|
||||
check(
|
||||
'头部窄屏默认收起(useState(!narrow) + 断点变化时重置)',
|
||||
/useState\(!narrow\)/.test(mv) && /setOpen\(!narrow\)/.test(mv),
|
||||
'缺少「窄屏默认收起」或跨断点重置 —— 自动隐藏只在首次生效'
|
||||
);
|
||||
check(
|
||||
'头部收起态保留标题与状态点,展开才有完整信息',
|
||||
mv.includes('aria-expanded={open}') &&
|
||||
mv.includes('min-w-0 flex-1 truncate text-sm font-semibold') &&
|
||||
mv.includes('{open && <div className="mt-1.5">{children}</div>}')
|
||||
);
|
||||
check(
|
||||
'回复框窄屏收起为悬浮球(reply-fab),点开才展开',
|
||||
mv.includes('data-testid="reply-fab"') &&
|
||||
mv.includes('narrow && !open') &&
|
||||
mv.includes('absolute bottom-4 right-4 z-20') &&
|
||||
mv.includes('<ChatBubbleIcon')
|
||||
);
|
||||
check(
|
||||
'回复框展开后有收回归口,且点开时焦点落到输入框',
|
||||
mv.includes('aria-label="收起回复框"') && mv.includes('autoFocus={narrow}')
|
||||
);
|
||||
check(
|
||||
'悬浮球与列表页新建球同一套观感(w-14 h-14 rounded-full)',
|
||||
(mv.match(/w-14 h-14 rounded-full/g) || []).length >= 1
|
||||
);
|
||||
|
||||
// 顶部标题行不能把返回键套进 toggle 按钮里(button 嵌 button 是无效 HTML,
|
||||
// 且屏幕阅读器会读出两个可点区域)
|
||||
check(
|
||||
'返回键在 toggle 按钮之外(避免 button 嵌套)',
|
||||
/\{lead\}\s*<button/.test(mv)
|
||||
);
|
||||
|
||||
console.log(failed === 0 ? '\n窄屏布局:全部通过' : `\n窄屏布局:${failed} 项失败`);
|
||||
process.exit(failed === 0 ? 0 : 1);
|
||||
|
||||
Reference in New Issue
Block a user