A11Y: Improve accessibility of likes/read count post buttons

The improvements are:

* Add an aria-label to the like/read count buttons below posts to
indicate what they mean and do.

* Add aria-pressed to the like/read count buttons to make it clear to screen
readers that these buttons are toggleable.

* Add an aria-label to the list of avatars that's shown when post likes
or readers are expanded so that screen reader users can understand what
the list of avatars means.
This commit is contained in:
OsamaSayegh 2022-03-29 14:41:11 +03:00 committed by Osama Sayegh
parent acdb64eb7e
commit fd26facdf3
5 changed files with 70 additions and 11 deletions

View File

@ -23,6 +23,14 @@ createWidget("small-user-list", {
return atts.listClassName;
},
buildAttributes(attrs) {
const attributes = { role: "list" };
if (attrs.ariaLabel) {
attributes["aria-label"] = attrs.ariaLabel;
}
return attributes;
},
html(atts) {
let users = atts.users;
if (users) {
@ -37,7 +45,11 @@ createWidget("small-user-list", {
let description = null;
if (atts.description) {
description = I18n.t(atts.description, { count: atts.count });
description = h(
"span",
{ attributes: { "aria-hidden": true } },
I18n.t(atts.description, { count: atts.count })
);
}
// oddly post_url is on the user
@ -46,10 +58,16 @@ createWidget("small-user-list", {
postUrl = postUrl || u.post_url;
if (u.unknown) {
return h("div.unknown", {
attributes: { title: I18n.t("post.unknown_user") },
attributes: {
title: I18n.t("post.unknown_user"),
role: "listitem",
},
});
} else {
return avatarFor.call(this, "small", u);
return avatarFor.call(this, "small", u, {
role: "listitem",
"aria-hidden": false,
});
}
});

View File

@ -45,6 +45,14 @@ export const ButtonClass = {
attributes["role"] = attrs.role;
}
if (attrs.translatedAriaLabel) {
attributes["aria-label"] = attrs.translatedAriaLabel;
}
if (attrs.ariaPressed) {
attributes["aria-pressed"] = attrs.ariaPressed;
}
if (attrs.tabAttrs) {
const tab = attrs.tabAttrs;
attributes["aria-selected"] = tab["aria-selected"];

View File

@ -5,6 +5,7 @@ import { formattedReminderTime } from "discourse/lib/bookmark";
import { h } from "virtual-dom";
import showModal from "discourse/lib/show-modal";
import { smallUserAtts } from "discourse/widgets/actions-summary";
import I18n from "I18n";
const LIKE_ACTION = 2;
const VIBRATE_DURATION = 5;
@ -64,10 +65,14 @@ export function buildButton(name, widget) {
}
}
registerButton("read-count", (attrs) => {
registerButton("read-count", (attrs, state) => {
if (attrs.showReadIndicator) {
const count = attrs.readCount;
if (count > 0) {
let ariaPressed = "false";
if (state?.readers && state.readers.length > 0) {
ariaPressed = "true";
}
return {
action: "toggleWhoRead",
title: "post.controls.read_indicator",
@ -75,6 +80,10 @@ registerButton("read-count", (attrs) => {
contents: count,
iconRight: true,
addContainer: false,
translatedAriaLabel: I18n.t("post.sr_post_read_count_button", {
count,
}),
ariaPressed,
};
}
}
@ -93,7 +102,7 @@ registerButton("read", (attrs) => {
}
});
function likeCount(attrs) {
function likeCount(attrs, state) {
const count = attrs.likeCount;
if (count > 0) {
@ -111,6 +120,10 @@ function likeCount(attrs) {
addContainer = true;
}
let ariaPressed = "false";
if (state?.likedUsers && state.likedUsers.length > 0) {
ariaPressed = "true";
}
return {
action: "toggleWhoLiked",
title,
@ -120,6 +133,8 @@ function likeCount(attrs) {
iconRight: true,
addContainer,
titleOptions: { count: attrs.liked ? count - 1 : count },
translatedAriaLabel: I18n.t("post.sr_post_like_count_button", { count }),
ariaPressed,
};
}
}
@ -630,6 +645,9 @@ export default createWidget("post-menu", {
listClassName: "who-read",
description,
count,
ariaLabel: I18n.t(
"post.actions.people.sr_post_readers_list_description"
),
})
);
}
@ -649,6 +667,9 @@ export default createWidget("post-menu", {
listClassName: "who-liked",
description,
count,
ariaLabel: I18n.t(
"post.actions.people.sr_post_likers_list_description"
),
})
);
}

View File

@ -68,16 +68,20 @@ export function avatarImg(wanted, attrs) {
return h("img", properties);
}
export function avatarFor(wanted, attrs) {
export function avatarFor(wanted, attrs, linkAttrs) {
const attributes = {
href: attrs.url,
"data-user-card": attrs.username,
"aria-hidden": true,
};
if (linkAttrs) {
Object.assign(attributes, linkAttrs);
}
return h(
"a",
{
className: `trigger-user-card ${attrs.className || ""}`,
attributes: {
href: attrs.url,
"data-user-card": attrs.username,
"aria-hidden": true,
},
attributes,
},
avatarImg(wanted, attrs)
);

View File

@ -3091,6 +3091,12 @@ en:
one: "you and %{count} other person liked this post"
other: "you and %{count} other people liked this post"
sr_post_like_count_button:
one: "%{count} person liked this post. Click to view"
other: "%{count} people liked this post. Click to view"
sr_post_read_count_button:
one: "%{count} person read this post. Click to view"
other: "%{count} people read this post. Click to view"
filtered_replies_hint:
one: "View this post and its reply"
other: "View this post and its %{count} replies"
@ -3201,6 +3207,8 @@ en:
read_capped:
one: "and %{count} other read this"
other: "and %{count} others read this"
sr_post_likers_list_description: "users who liked this post"
sr_post_readers_list_description: "users who read this post"
by_you:
off_topic: "You flagged this as off-topic"
spam: "You flagged this as spam"