feat(ui): P4 无障碍与打磨收尾
- P4-1 对比度:muted-soft 提到 WCAG AA 小字(≥4.5:1)于两主题各面 - P4-3 ConflictCard 原文/建议加 Minus/Plus 图标 + sr-only 标签(不靠颜色) - P4-4 动效:3 处 chevron transition-transform 加 motion-safe 门控 - P4-5 命令面板 ⌘K 键帽提示 + 键盘操作读数 + 空态引导 + mono 快捷键 - P2-8 SegmentedControl 升级 radiogroup(role=radio/aria-checked + 方向键 roving)
This commit is contained in:
@@ -66,6 +66,15 @@ export function useCommandPaletteOpen(): boolean {
|
||||
const LIST_ID = "command-list";
|
||||
const optionId = (cmd: Command): string => `command-option-${cmd.id}`;
|
||||
|
||||
// 统一的键帽(kbd)样式:暖纸感描边 + 等宽小字,与 NavDrawer 的 ⌘K 提示一致。
|
||||
function KeyCap({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<kbd className="rounded border border-line bg-panel px-1 font-mono text-2xs text-ink-soft">
|
||||
{children}
|
||||
</kbd>
|
||||
);
|
||||
}
|
||||
|
||||
// 从 pathname 抽取当前 projectId(/projects/<id>/...)。
|
||||
function projectIdFromPath(pathname: string): string | null {
|
||||
const m = pathname.match(/^\/projects\/([^/]+)/);
|
||||
@@ -208,20 +217,26 @@ export function CommandPalette({ pathname }: CommandPaletteProps) {
|
||||
if (dialogRef.current) handleTabTrap(dialogRef.current, e);
|
||||
}}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={onInputKey}
|
||||
placeholder="搜索命令或页面…(写本章 / 审稿 / 生成角色 / 跳转伏笔 / 搜设定)"
|
||||
role="combobox"
|
||||
aria-label="命令搜索"
|
||||
aria-expanded={true}
|
||||
aria-controls={LIST_ID}
|
||||
aria-activedescendant={activeCmd ? optionId(activeCmd) : undefined}
|
||||
aria-autocomplete="list"
|
||||
className={`w-full border-b border-line bg-bg px-4 py-3 text-sm text-ink ${focusRing}`}
|
||||
/>
|
||||
<div className="relative border-b border-line">
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
onKeyDown={onInputKey}
|
||||
placeholder="搜索命令或页面…(写本章 / 审稿 / 生成角色 / 跳转伏笔 / 搜设定)"
|
||||
role="combobox"
|
||||
aria-label="命令搜索"
|
||||
aria-expanded={true}
|
||||
aria-controls={LIST_ID}
|
||||
aria-activedescendant={activeCmd ? optionId(activeCmd) : undefined}
|
||||
aria-autocomplete="list"
|
||||
className={`w-full bg-bg py-3 pl-4 pr-12 text-sm text-ink ${focusRing}`}
|
||||
/>
|
||||
{/* 触发快捷键读数:随面板常驻,强化 ⌘K 的可发现性(纯装饰,不入 Tab 序)。 */}
|
||||
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<KeyCap>⌘K</KeyCap>
|
||||
</span>
|
||||
</div>
|
||||
<ul
|
||||
id={LIST_ID}
|
||||
role="listbox"
|
||||
@@ -229,7 +244,14 @@ export function CommandPalette({ pathname }: CommandPaletteProps) {
|
||||
className="max-h-80 overflow-auto py-1 overscroll-contain"
|
||||
>
|
||||
{results.length === 0 ? (
|
||||
<li className="px-4 py-3 text-sm text-ink-soft">无匹配命令</li>
|
||||
<li className="px-4 py-6 text-center">
|
||||
<p className="text-sm text-ink-soft">
|
||||
没有匹配「{query.trim()}」的命令
|
||||
</p>
|
||||
<p className="mt-1 text-caption text-muted-soft">
|
||||
试试「写本章」「审稿」「生成角色」「搜设定」
|
||||
</p>
|
||||
</li>
|
||||
) : (
|
||||
results.map((cmd, i) => (
|
||||
<li
|
||||
@@ -246,11 +268,30 @@ export function CommandPalette({ pathname }: CommandPaletteProps) {
|
||||
}`}
|
||||
>
|
||||
<span>{cmd.title}</span>
|
||||
<span className="text-xs text-ink-soft">{cmd.group}</span>
|
||||
<span className="text-caption text-ink-soft">{cmd.group}</span>
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</ul>
|
||||
{/* 键盘操作读数:↑↓ 选择 · ↵ 打开 · esc 关闭(常驻引导,纯装饰)。 */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="flex flex-wrap items-center gap-x-4 gap-y-1 border-t border-line px-4 py-2 text-caption text-muted-soft"
|
||||
>
|
||||
<span className="flex items-center gap-1">
|
||||
<KeyCap>↑</KeyCap>
|
||||
<KeyCap>↓</KeyCap>
|
||||
选择
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<KeyCap>↵</KeyCap>
|
||||
打开
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<KeyCap>esc</KeyCap>
|
||||
关闭
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user