diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index aef43f7..1b52c18 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -16,7 +16,8 @@ --color-body-strong: #3a342b; --color-body: #4a4234; --color-ink-soft: #6b6356; - --color-muted-soft: #8a8073; + /* muted-soft 达 WCAG AA 小字(≥4.5:1)于 bg/card/panel/soft 各面(P4-1)。 */ + --color-muted-soft: #6f6556; /* 边框:主边框 + 同带内更弱分隔 */ --color-line: #e5ddcd; --color-line-soft: #ece4d5; @@ -54,7 +55,8 @@ --color-body-strong: #ddd2c0; --color-body: #cdc0ab; --color-ink-soft: #b9ab96; - --color-muted-soft: #8f8474; + /* muted-soft 达 AA 小字(≥4.5:1)于夜读 bg/card/soft 各面(P4-1)。 */ + --color-muted-soft: #9a8e7c; /* 边框 */ --color-line: #3c352c; --color-line-soft: #2f2a23; diff --git a/apps/web/components/command/CommandPalette.tsx b/apps/web/components/command/CommandPalette.tsx index 028b148..2d3f5af 100644 --- a/apps/web/components/command/CommandPalette.tsx +++ b/apps/web/components/command/CommandPalette.tsx @@ -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 ( + + {children} + + ); +} + // 从 pathname 抽取当前 projectId(/projects//...)。 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); }} > - 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}`} - /> + + 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 序)。 */} + + ⌘K + + {results.length === 0 ? ( - 无匹配命令 + + + 没有匹配「{query.trim()}」的命令 + + + 试试「写本章」「审稿」「生成角色」「搜设定」 + + ) : ( results.map((cmd, i) => ( {cmd.title} - {cmd.group} + {cmd.group} )) )} + {/* 键盘操作读数:↑↓ 选择 · ↵ 打开 · esc 关闭(常驻引导,纯装饰)。 */} + + + ↑ + ↓ + 选择 + + + ↵ + 打开 + + + esc + 关闭 + + ); diff --git a/apps/web/components/review/ConflictCard.tsx b/apps/web/components/review/ConflictCard.tsx index 4ede673..193e2d5 100644 --- a/apps/web/components/review/ConflictCard.tsx +++ b/apps/web/components/review/ConflictCard.tsx @@ -1,6 +1,6 @@ "use client"; -import { AlertTriangle, CheckCircle2, LocateFixed, Sparkles } from "lucide-react"; +import { AlertTriangle, CheckCircle2, LocateFixed, Minus, Plus, Sparkles } from "lucide-react"; import { Badge } from "@/components/ui/Badge"; import { TextInput } from "@/components/ui/TextInput"; @@ -80,10 +80,22 @@ export function ConflictCard({ {conflict.original && conflict.replacement ? ( 改法: + {/* 非颜色线索:Minus/「原文」标签,色盲也可辨(不单靠红色 + 删除线) */} + + 原文: {conflict.original} → + {/* 非颜色线索:Plus/「建议」标签,色盲也可辨(不单靠绿色) */} + + 建议: {conflict.replacement} (点「采纳改法」改入终稿) diff --git a/apps/web/components/review/ReviewReport.tsx b/apps/web/components/review/ReviewReport.tsx index 33e66c0..5d240a4 100644 --- a/apps/web/components/review/ReviewReport.tsx +++ b/apps/web/components/review/ReviewReport.tsx @@ -604,7 +604,7 @@ export function ReviewReport({ : "已处理"} diff --git a/apps/web/components/review/ReviewSectionPanel.tsx b/apps/web/components/review/ReviewSectionPanel.tsx index ab3fb35..362b6e8 100644 --- a/apps/web/components/review/ReviewSectionPanel.tsx +++ b/apps/web/components/review/ReviewSectionPanel.tsx @@ -52,7 +52,7 @@ export function ReviewSectionPanel({ {statusLabel} ({ ariaLabel, className, }: SegmentedControlProps) { + const buttonsRef = useRef>([]); + + const focusOption = (index: number) => { + const clamped = (index + options.length) % options.length; + const target = options[clamped]; + if (!target) return; + onChange(target.value); + buttonsRef.current[clamped]?.focus(); + }; + + const handleKeyDown = (event: KeyboardEvent, index: number) => { + switch (event.key) { + case "ArrowRight": + case "ArrowDown": + event.preventDefault(); + focusOption(index + 1); + break; + case "ArrowLeft": + case "ArrowUp": + event.preventDefault(); + focusOption(index - 1); + break; + case "Home": + event.preventDefault(); + focusOption(0); + break; + case "End": + event.preventDefault(); + focusOption(options.length - 1); + break; + default: + break; + } + }; + return ( - - {options.map((option) => { + + {options.map((option, index) => { const selected = option.value === value; return ( { + buttonsRef.current[index] = node; + }} type="button" - aria-pressed={selected} + role="radio" + aria-checked={selected} + tabIndex={selected ? 0 : -1} onClick={() => onChange(option.value)} + onKeyDown={(event) => handleKeyDown(event, index)} className={cn( "rounded-md px-3 py-1.5 text-sm", transitionUi, diff --git a/apps/web/components/workbench/AiToolbar.tsx b/apps/web/components/workbench/AiToolbar.tsx index e707122..db0e0fe 100644 --- a/apps/web/components/workbench/AiToolbar.tsx +++ b/apps/web/components/workbench/AiToolbar.tsx @@ -63,7 +63,7 @@ export function DirectivePanel({ className={`group flex min-w-0 cursor-pointer items-center gap-2 rounded text-left font-serif text-title-md text-ink transition-colors duration-fast ease-standard hover:text-cinnabar ${focusRing}`} >
+ 没有匹配「{query.trim()}」的命令 +
+ 试试「写本章」「审稿」「生成角色」「搜设定」 +