Skip to content

Commit d303138

Browse files
author
Merge Resolver
committed
fix(webview-ui): accessible DisclosureHeader and refactor error/diff_error headers (aria, keyboard, chevron consistency)
1 parent eb4bd66 commit d303138

File tree

2 files changed

+115
-105
lines changed

2 files changed

+115
-105
lines changed

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 42 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import MarkdownBlock from "../common/MarkdownBlock"
3333
import { ReasoningBlock } from "./ReasoningBlock"
3434
import Thumbnails from "../common/Thumbnails"
3535
import McpResourceRow from "../mcp/McpResourceRow"
36-
import { IconButton } from "./IconButton"
36+
37+
import { DisclosureHeader } from "./DisclosureHeader"
3738

3839
import { Mention } from "./Mention"
3940
import { CheckpointSaved } from "./checkpoints/CheckpointSaved"
@@ -864,60 +865,29 @@ export const ChatRowContent = ({
864865
overflow: "hidden",
865866
marginBottom: "8px",
866867
}}>
867-
<div
868-
style={{
869-
borderBottom: isDiffErrorExpanded
870-
? "1px solid var(--vscode-editorGroup-border)"
871-
: "none",
872-
fontWeight: "normal",
873-
fontSize: "var(--vscode-font-size)",
874-
color: "var(--vscode-editor-foreground)",
875-
display: "flex",
876-
alignItems: "center",
877-
justifyContent: "space-between",
878-
cursor: "pointer",
868+
<DisclosureHeader
869+
contentId={`diff-error-${message.ts}`}
870+
iconClass="codicon-warning"
871+
iconStyle={{ color: "var(--vscode-editorWarning-foreground)", opacity: 0.8 }}
872+
title={<span style={{ fontWeight: "bold" }}>{t("chat:diffError.title")}</span>}
873+
expanded={isDiffErrorExpanded}
874+
onToggle={() => setIsDiffErrorExpanded(!isDiffErrorExpanded)}
875+
onCopy={() => {
876+
copyWithFeedback(message.text || "").then((success) => {
877+
if (success) {
878+
setShowCopySuccess(true)
879+
setTimeout(() => {
880+
setShowCopySuccess(false)
881+
}, 1000)
882+
}
883+
})
879884
}}
880-
onClick={() => setIsDiffErrorExpanded(!isDiffErrorExpanded)}>
881-
<div
882-
style={{
883-
display: "flex",
884-
alignItems: "center",
885-
gap: "10px",
886-
flexGrow: 1,
887-
}}>
888-
<span
889-
className="codicon codicon-warning"
890-
style={{
891-
color: "var(--vscode-editorWarning-foreground)",
892-
opacity: 0.8,
893-
fontSize: 16,
894-
marginBottom: "-1.5px",
895-
}}></span>
896-
<span style={{ fontWeight: "bold" }}>{t("chat:diffError.title")}</span>
897-
</div>
898-
<div style={{ display: "flex", alignItems: "center" }}>
899-
<IconButton
900-
iconClass={showCopySuccess ? "codicon-check" : "codicon-copy"}
901-
title={t("chat:codeblock.tooltips.copy_code")}
902-
onClick={(e: React.MouseEvent) => {
903-
e.stopPropagation()
904-
copyWithFeedback(message.text || "").then((success) => {
905-
if (success) {
906-
setShowCopySuccess(true)
907-
setTimeout(() => {
908-
setShowCopySuccess(false)
909-
}, 1000)
910-
}
911-
})
912-
}}
913-
style={{ marginRight: "4px" }}
914-
/>
915-
<span
916-
className={`codicon codicon-chevron-${isDiffErrorExpanded ? "up" : "down"}`}></span>
917-
</div>
918-
</div>
885+
copyTitle={t("chat:codeblock.tooltips.copy_code")}
886+
copyIconClass={showCopySuccess ? "codicon-check" : "codicon-copy"}
887+
/>
919888
{isDiffErrorExpanded && (
920889
<div
890+
id={`diff-error-${message.ts}`}
921891
style={{
922892
padding: "8px",
923893
backgroundColor: "var(--vscode-editor-background)",
@@ -1127,62 +1097,29 @@ export const ChatRowContent = ({
11271097
overflow: "hidden",
11281098
marginBottom: "8px",
11291099
}}>
1130-
<div
1131-
style={{
1132-
borderBottom: isErrorExpanded
1133-
? "1px solid var(--vscode-editorGroup-border)"
1134-
: "none",
1135-
fontWeight: "normal",
1136-
fontSize: "var(--vscode-font-size)",
1137-
color: "var(--vscode-editor-foreground)",
1138-
display: "flex",
1139-
alignItems: "center",
1140-
justifyContent: "space-between",
1141-
cursor: "pointer",
1100+
<DisclosureHeader
1101+
contentId={`error-${message.ts}`}
1102+
iconClass="codicon-error"
1103+
iconStyle={{ color: "var(--vscode-errorForeground)", opacity: 0.8 }}
1104+
title={<span style={{ fontWeight: "bold" }}>{t("chat:error")}</span>}
1105+
expanded={isErrorExpanded}
1106+
onToggle={() => setIsErrorExpanded(!isErrorExpanded)}
1107+
onCopy={() => {
1108+
copyWithFeedback(message.text || "").then((success) => {
1109+
if (success) {
1110+
setShowErrorCopySuccess(true)
1111+
setTimeout(() => {
1112+
setShowErrorCopySuccess(false)
1113+
}, 1000)
1114+
}
1115+
})
11421116
}}
1143-
onClick={() => setIsErrorExpanded(!isErrorExpanded)}>
1144-
<div
1145-
style={{
1146-
display: "flex",
1147-
alignItems: "center",
1148-
gap: "10px",
1149-
flexGrow: 1,
1150-
}}>
1151-
<span
1152-
className="codicon codicon-error"
1153-
style={{
1154-
color: "var(--vscode-errorForeground)",
1155-
opacity: 0.8,
1156-
fontSize: 16,
1157-
marginBottom: "-1.5px",
1158-
}}></span>
1159-
<span style={{ fontWeight: "bold", color: "var(--vscode-errorForeground)" }}>
1160-
{t("chat:error")}
1161-
</span>
1162-
</div>
1163-
<div style={{ display: "flex", alignItems: "center" }}>
1164-
<IconButton
1165-
iconClass={showErrorCopySuccess ? "codicon-check" : "codicon-copy"}
1166-
title={t("chat:codeblock.tooltips.copy_code")}
1167-
onClick={(e: React.MouseEvent) => {
1168-
e.stopPropagation()
1169-
copyWithFeedback(message.text || "").then((success) => {
1170-
if (success) {
1171-
setShowErrorCopySuccess(true)
1172-
setTimeout(() => {
1173-
setShowErrorCopySuccess(false)
1174-
}, 1000)
1175-
}
1176-
})
1177-
}}
1178-
style={{ marginRight: "4px" }}
1179-
/>
1180-
<span
1181-
className={`codicon codicon-chevron-${isErrorExpanded ? "up" : "down"}`}></span>
1182-
</div>
1183-
</div>
1117+
copyTitle={t("chat:codeblock.tooltips.copy_code")}
1118+
copyIconClass={showErrorCopySuccess ? "codicon-check" : "codicon-copy"}
1119+
/>
11841120
{isErrorExpanded && (
11851121
<div
1122+
id={`error-${message.ts}`}
11861123
style={{
11871124
padding: "8px",
11881125
backgroundColor: "var(--vscode-editor-background)",
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from "react"
2+
import { cn } from "@/lib/utils"
3+
import { IconButton } from "./IconButton"
4+
5+
interface DisclosureHeaderProps {
6+
contentId: string
7+
iconClass: string
8+
iconStyle?: React.CSSProperties
9+
title: React.ReactNode
10+
expanded: boolean
11+
onToggle: () => void
12+
onCopy?: (e: React.MouseEvent) => void
13+
copyTitle?: string
14+
copyIconClass?: string // e.g. "codicon-copy" | "codicon-check"
15+
className?: string
16+
}
17+
18+
export const DisclosureHeader: React.FC<DisclosureHeaderProps> = ({
19+
contentId,
20+
iconClass,
21+
iconStyle,
22+
title,
23+
expanded,
24+
onToggle,
25+
onCopy,
26+
copyTitle,
27+
copyIconClass,
28+
className,
29+
}) => {
30+
return (
31+
<div
32+
className={cn("flex items-center justify-between", className)}
33+
style={{
34+
fontWeight: "normal",
35+
fontSize: "var(--vscode-font-size)",
36+
color: "var(--vscode-editor-foreground)",
37+
borderBottom: expanded ? "1px solid var(--vscode-editorGroup-border)" : "none",
38+
}}>
39+
<button
40+
type="button"
41+
aria-expanded={expanded}
42+
aria-controls={contentId}
43+
onClick={onToggle}
44+
className={cn(
45+
"flex items-center justify-between gap-2",
46+
"bg-transparent border-0 p-0 m-0 cursor-pointer",
47+
"focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder",
48+
)}
49+
style={{ width: "100%", textAlign: "left" }}>
50+
<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
51+
<span
52+
className={cn("codicon", iconClass)}
53+
style={{ fontSize: 16, marginBottom: "-1.5px", ...iconStyle }}
54+
/>
55+
<span>{title}</span>
56+
</div>
57+
<span className={`codicon codicon-chevron-${expanded ? "down" : "right"}`} />
58+
</button>
59+
60+
{onCopy && (
61+
<IconButton
62+
iconClass={copyIconClass ?? "codicon-copy"}
63+
title={copyTitle ?? "Copy"}
64+
onClick={(e) => {
65+
e.stopPropagation()
66+
onCopy(e)
67+
}}
68+
style={{ marginLeft: 4 }}
69+
/>
70+
)}
71+
</div>
72+
)
73+
}

0 commit comments

Comments
 (0)