Skip to content

Commit 55489d7

Browse files
committed
Add screen reader announcement message after CopyCodeButton completes action
1 parent aa23823 commit 55489d7

File tree

1 file changed

+77
-48
lines changed

1 file changed

+77
-48
lines changed

src/components/CopyCodeButton/index.tsx

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,33 @@ import CircleButton from "../CircleButton";
33

44
interface CopyCodeButtonProps {
55
textToCopy: string;
6+
announceOnCopy?: string;
67
}
78

8-
export const CopyCodeButton = ({ textToCopy }: CopyCodeButtonProps) => {
9+
export const CopyCodeButton = ({
10+
textToCopy,
11+
announceOnCopy = 'Code copied to clipboard'
12+
}: CopyCodeButtonProps) => {
913
const [isCopied, setIsCopied] = useState(false);
1014

15+
// const liveId = useId();
16+
const [liveId] = useState(
17+
() => 'copy-live-' + Math.random().toString(36).slice(2)
18+
);
19+
20+
/** Write a message into the live region so screen readers will announce it */
21+
const announce = (message: string) => {
22+
const region = document.getElementById(liveId) as HTMLSpanElement | null;
23+
if (!region) return;
24+
25+
region.textContent = message;
26+
27+
// Clear the text after 1 s so a future announcement will fire again
28+
setTimeout(() => {
29+
region.textContent = '';
30+
}, 1000);
31+
};
32+
1133
const copyTextToClipboard = async () => {
1234
console.log('Copy button clicked');
1335
console.log('Text to copy:', textToCopy);
@@ -16,6 +38,9 @@ export const CopyCodeButton = ({ textToCopy }: CopyCodeButtonProps) => {
1638
console.log('Using Clipboard API');
1739
await navigator.clipboard.writeText(textToCopy);
1840
console.log('Text copied successfully');
41+
42+
announce(announceOnCopy);
43+
1944
setIsCopied(true);
2045
setTimeout(() => {
2146
setIsCopied(false);
@@ -29,52 +54,56 @@ export const CopyCodeButton = ({ textToCopy }: CopyCodeButtonProps) => {
2954
console.log('Component rendered, isCopied:', isCopied);
3055

3156
return (
32-
<CircleButton
33-
onClick={() => {
34-
console.log('CircleButton clicked');
35-
copyTextToClipboard();
36-
}}
37-
ariaLabel="Copy code to clipboard"
38-
className={`bg-white ${isCopied ? 'text-green-600' : 'text-black'} transition-colors duration-200`}
39-
>
40-
{isCopied ? (
41-
<svg
42-
width="18"
43-
height="22"
44-
viewBox="0 0 24 24"
45-
fill="none"
46-
xmlns="http://www.w3.org/2000/svg"
47-
>
48-
<path
49-
d="M20 6L9 17L4 12"
50-
stroke="currentColor"
51-
strokeWidth="2"
52-
strokeLinecap="round"
53-
strokeLinejoin="round"
54-
/>
55-
</svg>
56-
) : (
57-
<svg
58-
width="18"
59-
height="22"
60-
viewBox="4 7 18 23"
61-
fill="none"
62-
xmlns="http://www.w3.org/2000/svg"
63-
>
64-
<path
65-
fillRule="evenodd"
66-
clipRule="evenodd"
67-
d="M 4.054 12.141 C 4.054 11.865 4.877 11.877 5.153 11.877 L 9.073 11.953 C 9.2 11.953 8.791 22.207 9.006 23.531 C 11.73 24.182 17.631 24.022 17.631 24.171 L 17.638 28.083 C 17.638 28.359 17.414 28.583 17.138 28.583 L 4.554 28.583 C 4.278 28.583 4.054 28.359 4.054 28.083 L 4.054 12.141 Z M 5.054 12.641 L 5.054 27.583 L 16.638 27.583 L 16.735 24.024 L 8.623 24.051 L 8.195 12.679 L 5.054 12.641 Z"
68-
fill="currentColor"
69-
/>
70-
<path
71-
fillRule="evenodd"
72-
clipRule="evenodd"
73-
d="M 8.14 8.083 C 8.14 7.807 8.364 7.583 8.64 7.583 L 21.224 7.583 C 21.5 7.583 21.724 7.807 21.724 8.083 L 21.724 24.025 C 21.724 24.301 21.5 24.525 21.224 24.525 L 8.64 24.525 C 8.364 24.525 8.14 24.301 8.14 24.025 L 8.14 8.083 Z M 9.14 8.583 L 9.14 23.525 L 20.724 23.525 L 20.724 8.583 L 9.14 8.583 Z"
74-
fill="currentColor"
75-
/>
76-
</svg>
77-
)}
78-
</CircleButton>
57+
<>
58+
<CircleButton
59+
onClick={() => {
60+
console.log('CircleButton clicked');
61+
copyTextToClipboard();
62+
}}
63+
ariaLabel="Copy code to clipboard"
64+
className={`bg-white ${isCopied ? 'text-green-600' : 'text-black'} transition-colors duration-200`}
65+
>
66+
{isCopied ? (
67+
<svg
68+
width="18"
69+
height="22"
70+
viewBox="0 0 24 24"
71+
fill="none"
72+
xmlns="http://www.w3.org/2000/svg"
73+
>
74+
<path
75+
d="M20 6L9 17L4 12"
76+
stroke="currentColor"
77+
strokeWidth="2"
78+
strokeLinecap="round"
79+
strokeLinejoin="round"
80+
/>
81+
</svg>
82+
) : (
83+
<svg
84+
width="18"
85+
height="22"
86+
viewBox="4 7 18 23"
87+
fill="none"
88+
xmlns="http://www.w3.org/2000/svg"
89+
>
90+
<path
91+
fillRule="evenodd"
92+
clipRule="evenodd"
93+
d="M 4.054 12.141 C 4.054 11.865 4.877 11.877 5.153 11.877 L 9.073 11.953 C 9.2 11.953 8.791 22.207 9.006 23.531 C 11.73 24.182 17.631 24.022 17.631 24.171 L 17.638 28.083 C 17.638 28.359 17.414 28.583 17.138 28.583 L 4.554 28.583 C 4.278 28.583 4.054 28.359 4.054 28.083 L 4.054 12.141 Z M 5.054 12.641 L 5.054 27.583 L 16.638 27.583 L 16.735 24.024 L 8.623 24.051 L 8.195 12.679 L 5.054 12.641 Z"
94+
fill="currentColor"
95+
/>
96+
<path
97+
fillRule="evenodd"
98+
clipRule="evenodd"
99+
d="M 8.14 8.083 C 8.14 7.807 8.364 7.583 8.64 7.583 L 21.224 7.583 C 21.5 7.583 21.724 7.807 21.724 8.083 L 21.724 24.025 C 21.724 24.301 21.5 24.525 21.224 24.525 L 8.64 24.525 C 8.364 24.525 8.14 24.301 8.14 24.025 L 8.14 8.083 Z M 9.14 8.583 L 9.14 23.525 L 20.724 23.525 L 20.724 8.583 L 9.14 8.583 Z"
100+
fill="currentColor"
101+
/>
102+
</svg>
103+
)}
104+
</CircleButton>
105+
{/* Visually hidden live region for accessibility announcements */}
106+
<span id={liveId} aria-live="polite" class="sr-only" />
107+
</>
79108
);
80109
};

0 commit comments

Comments
 (0)