Skip to content

Commit 3055526

Browse files
committed
frontend/aria: page in general, lighthouse accessibility testing, ...
1 parent 7e62e50 commit 3055526

File tree

19 files changed

+401
-2005
lines changed

19 files changed

+401
-2005
lines changed

src/dev/ARIA.md

Lines changed: 304 additions & 1966 deletions
Large diffs are not rendered by default.

src/packages/frontend/account/avatar/avatar.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,13 @@ const Avatar0: React.FC<Props> = (props) => {
234234

235235
function render_inside() {
236236
if (image) {
237-
return <img style={{ borderRadius: "50%", width: "100%" }} src={image} />;
237+
return (
238+
<img
239+
alt={`User ${get_name()}`}
240+
style={{ borderRadius: "50%", width: "100%" }}
241+
src={image}
242+
/>
243+
);
238244
} else {
239245
return render_letter();
240246
}

src/packages/frontend/app/i18n-banner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function useShowI18NBanner() {
5252
const i18n = other_settings?.get(OTHER_SETTINGS_LOCALE_KEY);
5353

5454
return useMemo(() => {
55-
// we show the banner, if the default locale is set and the browser langauge is not english
55+
// we show the banner, if the default locale is set and the browser language is not english
5656
// user's can dismiss this, which sets the locale to "en-keep".
5757
if (i18n === DEFAULT_LOCALE) {
5858
if (!navigator.language.toLowerCase().startsWith("en")) {

src/packages/frontend/app/localize.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ export function Localize({ children }: { children: React.ReactNode }) {
5353
setAntdLoc(await loadAntdLocale(locale));
5454
}, [locale]);
5555

56+
// Update HTML lang attribute for screen readers (WCAG AA)
57+
useAsyncEffect(async () => {
58+
document.documentElement.lang = locale;
59+
}, [locale]);
60+
5661
function renderApp() {
5762
// NOTE: the locale will be set from the other_settings, on the "page".
5863
// So, for the default (english) we always have to render it, and then, maybe, a locale is set...

src/packages/frontend/app/logo.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ export const AppLogo: React.FC<Props> = React.memo((props: Props) => {
2828

2929
const logo_square: string | undefined = useTypedRedux(
3030
"customize",
31-
"logo_square"
31+
"logo_square",
3232
);
3333

3434
const backgroundImage = `url('${logo_square ? logo_square : APP_ICON}')`;
3535

3636
return (
3737
<A
3838
href={appBasePath}
39+
aria-label="CoCalc homepage"
3940
style={{
4041
height: dimension,
4142
width: dimension,

src/packages/frontend/app/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export const Page: React.FC = () => {
230230
icon={"users"}
231231
active_top_tab={active_top_tab}
232232
hide_label={!show_label}
233+
aria-label="Admin"
233234
/>
234235
);
235236
}

src/packages/frontend/browser.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,31 @@ export function set_window_title(title?: string): void {
4040
document.title = site_name;
4141
}
4242
}
43+
44+
// Set the meta description tag dynamically (WCAG AA)
45+
export function set_meta_description(): void {
46+
const customize = redux.getStore("customize");
47+
const site_name = customize.get("site_name");
48+
const site_description = customize.get("site_description");
49+
50+
let description = "";
51+
if (site_name && site_description) {
52+
description = `${site_name}: ${site_description}`;
53+
} else if (site_name) {
54+
description = site_name;
55+
} else if (site_description) {
56+
description = site_description;
57+
}
58+
59+
if (description.length > 0) {
60+
let metaTag = document.querySelector(
61+
'meta[name="description"]',
62+
) as HTMLMetaElement | null;
63+
if (metaTag == null) {
64+
metaTag = document.createElement("meta");
65+
metaTag.name = "description";
66+
document.head.appendChild(metaTag);
67+
}
68+
metaTag.content = description;
69+
}
70+
}

src/packages/frontend/components/A.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface AProps {
2020
style?: CSSProperties;
2121
onClick?: (any) => void;
2222
onMouseDown?: (any) => void;
23+
"aria-label"?: string;
2324
}
2425

2526
export function A({
@@ -30,6 +31,7 @@ export function A({
3031
placement,
3132
onClick,
3233
onMouseDown,
34+
"aria-label": ariaLabel,
3335
}: AProps) {
3436
if (title) {
3537
// use nicer antd tooltip.
@@ -40,6 +42,7 @@ export function A({
4042
target={"_blank"}
4143
rel={"noopener"}
4244
style={style}
45+
aria-label={ariaLabel}
4346
onClick={onClick}
4447
onMouseDown={onMouseDown}
4548
>
@@ -55,6 +58,7 @@ export function A({
5558
rel={"noopener"}
5659
style={style}
5760
title={title}
61+
aria-label={ariaLabel}
5862
onClick={onClick}
5963
onMouseDown={onMouseDown}
6064
>

src/packages/frontend/components/language-model-icon.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ export function LanguageModelVendorAvatar(
4545
return fallback();
4646
}
4747

48-
function renderImgIcon(icon: string) {
48+
function renderImgIcon(icon: string, vendorName: string) {
4949
return (
5050
<img
51+
alt={`${vendorName} language model`}
5152
width={size}
5253
height={size}
5354
src={icon}
@@ -69,7 +70,7 @@ export function LanguageModelVendorAvatar(
6970
"icon",
7071
]);
7172
if (useIcon && typeof icon === "string") {
72-
return renderImgIcon(icon);
73+
return renderImgIcon(icon, vendorName);
7374
} else {
7475
return <OpenAIAvatar size={size} style={style} />;
7576
}
@@ -92,7 +93,7 @@ export function LanguageModelVendorAvatar(
9293
case "ollama": {
9394
const icon = ollama?.getIn([fromOllamaModel(model), "icon"]);
9495
if (useIcon && typeof icon === "string") {
95-
return renderImgIcon(icon);
96+
return renderImgIcon(icon, vendorName);
9697
} else {
9798
return <OllamaAvatar size={size} style={style} />;
9899
}

src/packages/frontend/components/sortable-tabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function SortableTabs(props: Props) {
112112
}, [resize.width, items.length, divRef.current, isOver]);
113113

114114
return (
115-
<div style={{ width: "100%", ...style }} ref={divRef}>
115+
<div role="tablist" style={{ width: "100%", ...style }} ref={divRef}>
116116
<ItemContext.Provider value={{ width: itemWidth }}>
117117
<DndContext
118118
onDragStart={onDragStart}

0 commit comments

Comments
 (0)