Skip to content

Commit a99c48d

Browse files
authored
Merge branch '2.0' into pr-766-20
2 parents 456b8b5 + 813ae4d commit a99c48d

File tree

11 files changed

+153
-107
lines changed

11 files changed

+153
-107
lines changed

src/components/Banner/index.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const { link, title } = entry.data;
99
<a class="banner-content" href={link} target="_blank">
1010
<Content />
1111
</a>
12-
<button id="hideBanner"><Icon kind="close"></button>
12+
<button id="hideBanner" aria-label="Hide banner"><Icon kind="close" /></button>
1313
</div>
1414

1515
<script>

src/components/CodeEmbed/frame.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export const CodeFrame = (props: CodeFrameProps) => {
163163
base: props.base,
164164
scripts: props.scripts,
165165
instanceMode: props.jsCode.includes('new p5'),
166-
}) : undefined}
166+
}) : ""}
167167
sandbox="allow-scripts allow-popups allow-modals allow-forms allow-same-origin"
168168
aria-label="Code Preview"
169169
title="Code Preview"

src/components/CodeEmbed/index.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useEffect, useRef } from "preact/hooks";
2+
import { useLiveRegion } from '../hooks/useLiveRegion';
23
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
34
import { javascript } from "@codemirror/lang-javascript";
45
import { cdnLibraryUrl, cdnSoundUrl } from "@/src/globals/globals";
@@ -25,6 +26,7 @@ import { Icon } from "../Icon";
2526
* }
2627
*/
2728
export const CodeEmbed = (props) => {
29+
const { ref: liveRegionRef, announce } = useLiveRegion();
2830
const [rendered, setRendered] = useState(false);
2931
const initialCode = props.initialValue ?? "";
3032
// Source code from Google Docs sometimes uses a unicode non-breaking space
@@ -59,6 +61,7 @@ export const CodeEmbed = (props) => {
5961
} else {
6062
setPreviewCodeString(codeString);
6163
}
64+
announce("Sketch is running");
6265
};
6366

6467
const [previewCodeString, setPreviewCodeString] = useState(codeString);
@@ -108,6 +111,7 @@ export const CodeEmbed = (props) => {
108111
className="bg-bg-gray-40"
109112
onClick={() => {
110113
setPreviewCodeString("");
114+
announce("Sketch stopped");
111115
}}
112116
ariaLabel="Stop sketch"
113117
>
@@ -148,6 +152,7 @@ export const CodeEmbed = (props) => {
148152
onClick={() => {
149153
setCodeString(initialCode);
150154
setPreviewCodeString(initialCode);
155+
announce("Code reset to initial value.");
151156
}}
152157
ariaLabel="Reset code to initial value"
153158
className="bg-white text-black"
@@ -156,6 +161,7 @@ export const CodeEmbed = (props) => {
156161
</CircleButton>
157162
</div>
158163
</div>
164+
<span ref={liveRegionRef} aria-live="polite" class="sr-only" />
159165
</div>
160166
);
161167
};
Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { useState } from 'preact/hooks';
2+
import { useLiveRegion } from '../hooks/useLiveRegion';
23
import CircleButton from "../CircleButton";
34

45
interface CopyCodeButtonProps {
56
textToCopy: string;
7+
announceOnCopy?: string;
68
}
79

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

16+
const { ref: liveRegionRef, announce } = useLiveRegion<HTMLSpanElement>();
17+
1118
const copyTextToClipboard = async () => {
1219
console.log('Copy button clicked');
1320
console.log('Text to copy:', textToCopy);
@@ -16,6 +23,9 @@ export const CopyCodeButton = ({ textToCopy }: CopyCodeButtonProps) => {
1623
console.log('Using Clipboard API');
1724
await navigator.clipboard.writeText(textToCopy);
1825
console.log('Text copied successfully');
26+
27+
announce(announceOnCopy);
28+
1929
setIsCopied(true);
2030
setTimeout(() => {
2131
setIsCopied(false);
@@ -29,52 +39,56 @@ export const CopyCodeButton = ({ textToCopy }: CopyCodeButtonProps) => {
2939
console.log('Component rendered, isCopied:', isCopied);
3040

3141
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>
42+
<>
43+
<CircleButton
44+
onClick={() => {
45+
console.log('CircleButton clicked');
46+
copyTextToClipboard();
47+
}}
48+
ariaLabel="Copy code to clipboard"
49+
className={`bg-white ${isCopied ? 'text-green-600' : 'text-black'} transition-colors duration-200`}
50+
>
51+
{isCopied ? (
52+
<svg
53+
width="18"
54+
height="22"
55+
viewBox="0 0 24 24"
56+
fill="none"
57+
xmlns="http://www.w3.org/2000/svg"
58+
>
59+
<path
60+
d="M20 6L9 17L4 12"
61+
stroke="currentColor"
62+
strokeWidth="2"
63+
strokeLinecap="round"
64+
strokeLinejoin="round"
65+
/>
66+
</svg>
67+
) : (
68+
<svg
69+
width="18"
70+
height="22"
71+
viewBox="4 7 18 23"
72+
fill="none"
73+
xmlns="http://www.w3.org/2000/svg"
74+
>
75+
<path
76+
fillRule="evenodd"
77+
clipRule="evenodd"
78+
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"
79+
fill="currentColor"
80+
/>
81+
<path
82+
fillRule="evenodd"
83+
clipRule="evenodd"
84+
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"
85+
fill="currentColor"
86+
/>
87+
</svg>
88+
)}
89+
</CircleButton>
90+
{/* Visually hidden live region for accessibility announcements */}
91+
<span ref={liveRegionRef} aria-live="polite" class="sr-only" />
92+
</>
7993
);
8094
};

src/components/Nav/JumpToLinks.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ export const JumpToLinks = ({
2222
<button
2323
class={styles.toggle}
2424
onClick={handleToggle}
25-
aria-hidden="true"
26-
tabIndex={-1}
25+
aria-expanded={isOpen}
26+
aria-label={`${heading} menu toggle`}
2727
>
2828
<span>{heading}</span>
2929
<div class="pt-[6px]">
3030
<Icon kind={isOpen ? "chevron-down" : "chevron-up"} />
3131
</div>
3232
</button>
33+
3334
{isOpen && (
3435
<ul>
3536
{links?.map((link) => (

src/components/Nav/MainNavLinks.tsx

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ export const MainNavLinks = ({
4545
<button
4646
class={styles.toggle}
4747
onClick={handleToggle}
48-
aria-hidden="true"
49-
tabIndex={-1}
48+
aria-expanded={isOpen}
49+
aria-label={mobileMenuLabel || "Toggle navigation menu"}
5050
>
5151
<div class={styles.mobileMenuLabel}>
5252
{isOpen ? (
@@ -79,26 +79,24 @@ export const MainNavLinks = ({
7979
</li>
8080
))}
8181
</ul>
82-
{
83-
<ul class="flex flex-col gap-[15px]">
84-
<li>
85-
<a className={styles.buttonlink} href="https://editor.p5js.org">
86-
<div class="mr-xxs">
87-
<Icon kind="code-brackets" />
88-
</div>
89-
{editorButtonLabel}
90-
</a>
91-
</li>
92-
<li>
93-
<a className={styles.buttonlink} href="/donate/">
94-
<div class="mr-xxs">
95-
<Icon kind="heart" />
96-
</div>
97-
{donateButtonLabel}
98-
</a>
99-
</li>
100-
</ul>
101-
}
82+
<ul class="flex flex-col gap-[15px]">
83+
<li>
84+
<a className={styles.buttonlink} href="https://editor.p5js.org">
85+
<div class="mr-xxs">
86+
<Icon kind="code-brackets" />
87+
</div>
88+
{editorButtonLabel}
89+
</a>
90+
</li>
91+
<li>
92+
<a className={styles.buttonlink} href="/donate/">
93+
<div class="mr-xxs">
94+
<Icon kind="heart" />
95+
</div>
96+
{donateButtonLabel}
97+
</a>
98+
</li>
99+
</ul>
102100
</div>
103101
);
104102
};

src/components/PageHeader/HomePage.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ const { config } = Astro.props;
3939
{
4040
config.data.heroImages.map((im, i) => (
4141
im.linkTarget ?
42-
<a href={im.linkTarget }>
42+
<a href={im.linkTarget} class={`hero-image-container ${i > 0 ? "hidden" : ""}`}>
4343
<Image
44-
containerClass={`relative hero-image-container ${i > 0 ? "hidden" : ""}`}
44+
containerClass={"relative"}
4545
class={"hero-image"}
4646
aspectRatio="none"
4747
src={im.image}

src/components/ReferenceDirectoryWithFilter/index.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,10 @@ export const ReferenceDirectoryWithFilter = ({
221221
}}
222222
/>
223223
{searchKeyword.length > 0 && (
224-
<button type="reset" class="" onClick={clearInput}>
225-
<Icon kind="close" className="h-4 w-4" />
226-
</button>
224+
<button type="reset" class="" onClick={clearInput} aria-label="Clear search input">
225+
<Icon kind="close" className="h-4 w-4" />
226+
</button>
227+
227228
)}
228229
</div>
229230
</div>

src/components/SearchResults/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ const SearchResults = ({
7777
value={category}
7878
className="capitalize"
7979
onClick={() => toggleFilter(category)}
80+
aria-pressed={currentFilter === category}
81+
aria-label={`Filter by ${uiTranslations[uiTranslationKey(category)]}`}
8082
>
8183
<div class="flex flex-nowrap gap-xs">
8284
{uiTranslations[uiTranslationKey(category)]}
@@ -131,6 +133,7 @@ const SearchResults = ({
131133
type="submit"
132134
class="absolute right-0 top-[2px] px-[22px] py-[13px]"
133135
onClick={submitInput}
136+
aria-label="Submit search"
134137
>
135138
<Icon kind="arrow-lg" />
136139
</button>
@@ -139,6 +142,7 @@ const SearchResults = ({
139142
type="reset"
140143
class="absolute right-0 top-0 px-[22px] py-[13px]"
141144
onClick={clearInput}
145+
aria-label="Clear search input"
142146
>
143147
<Icon kind="close-lg" />
144148
</button>

src/components/hooks/useLiveRegion.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useRef, useEffect } from 'preact/hooks';
2+
3+
export function useLiveRegion<T extends HTMLElement = HTMLElement>() {
4+
const ref = useRef<T | null>(null);
5+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
6+
7+
/** Clear any existing timer */
8+
const clearTimer = () => {
9+
if (timerRef.current !== null) {
10+
clearTimeout(timerRef.current);
11+
timerRef.current = null;
12+
}
13+
if (ref.current) ref.current.textContent = '';
14+
};
15+
16+
const announce = (message: string, clearMessage = 1000) => {
17+
const node = ref.current;
18+
if (!node) return;
19+
clearTimer();
20+
node.textContent = message;
21+
timerRef.current = setTimeout(() => {
22+
if (node) node.textContent = '';
23+
timerRef.current = null;
24+
}, clearMessage);
25+
};
26+
27+
useEffect(() => clearTimer, []);
28+
29+
return { ref, announce };
30+
}

0 commit comments

Comments
 (0)