A11Y: fix and improve user card accessibility (#29399)

This commit is contained in:
Kris 2024-10-25 12:43:43 -04:00 committed by GitHub
parent 92cd2818ad
commit 74bb520877
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 31 additions and 28 deletions

View File

@ -193,7 +193,7 @@ export default class CardContentsBase extends Component {
return this._show(target.innerText.replace(/^@/, ""), target, event);
}
_positionCard(target, event) {
_positionCard(target) {
schedule("afterRender", async () => {
if (this.site.desktopView) {
this._menuInstance = await this.menu.show(target, {
@ -228,11 +228,10 @@ export default class CardContentsBase extends Component {
// note: we DO NOT use afterRender here cause _positionCard may
// run afterwards, if we allowed this to happen the usercard
// may be offscreen and we may scroll all the way to it on focus
if (event?.pointerId === -1) {
discourseLater(() => {
this.element.querySelector("a")?.focus();
this.element.querySelector("a.user-profile-link")?.focus();
}, 350);
}
});
}

View File

@ -27,7 +27,7 @@
</div>
{{else}}
<div class="card-row first-row">
<div class="user-card-avatar">
<div class="user-card-avatar" aria-hidden="true">
{{#if this.contentHidden}}
<span class="card-huge-avatar">{{bound-avatar
this.user
@ -38,6 +38,7 @@
{{on "click" this.handleShowUser}}
href={{this.user.path}}
class="card-huge-avatar"
tabindex="-1"
>{{bound-avatar this.user "huge"}}</a>
{{/if}}
@ -59,10 +60,7 @@
{{if this.nameFirst 'full-name' 'username'}}"
>
{{#if this.contentHidden}}
<span
id="discourse-user-card-title"
class="name-username-wrapper"
>
<span class="name-username-wrapper">
{{if
this.nameFirst
this.user.name
@ -74,11 +72,12 @@
{{on "click" this.handleShowUser}}
href={{this.user.path}}
class="user-profile-link"
aria-label={{i18n
"user.profile_link"
username=this.user.username
}}
>
<span
id="discourse-user-card-title"
class="name-username-wrapper"
>
<span class="name-username-wrapper">
{{if
this.nameFirst
this.user.name
@ -185,13 +184,13 @@
{{#if this.user.profile_hidden}}
<div class="card-row second-row">
<div class="profile-hidden">
<span>{{i18n "user.profile_hidden"}}</span>
<span role="alert">{{i18n "user.profile_hidden"}}</span>
</div>
</div>
{{else if this.user.inactive}}
<div class="card-row second-row">
<div class="inactive-user">
<span>{{i18n "user.inactive_user"}}</span>
<span role="alert">{{i18n "user.inactive_user"}}</span>
</div>
</div>
{{/if}}

View File

@ -31,7 +31,7 @@ import I18n from "discourse-i18n";
"usernameClass",
"primaryGroup"
)
@attributeBindings("labelledBy:aria-labelledby")
@attributeBindings("ariaLabel:aria-label")
export default class UserCardContents extends CardContentsBase.extend(
CanCheckEmails,
CleansUp
@ -40,6 +40,7 @@ export default class UserCardContents extends CardContentsBase.extend(
avatarSelector = "[data-user-card]";
avatarDataAttrKey = "userCard";
mentionSelector = "a.mention";
ariaLabel = I18n.t("user.card");
@setting("allow_profile_backgrounds") allowBackgrounds;
@setting("enable_badges") showBadges;
@ -75,11 +76,6 @@ export default class UserCardContents extends CardContentsBase.extend(
return this.user.name !== this.user.username;
}
@discourseComputed("user")
labelledBy(user) {
return user ? "discourse-user-card-title" : null;
}
@discourseComputed("user")
hasLocaleOrWebsite(user) {
return user.location || user.website_name || this.userTimezone;

View File

@ -3,7 +3,7 @@ import Modifier from "ember-modifier";
import { bind } from "discourse-common/utils/decorators";
const FOCUSABLE_ELEMENTS =
'details:not(.is-disabled) summary, [autofocus], a, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])';
"details:not(.is-disabled) summary, [autofocus], a, input, select, textarea, summary";
export default class TrapTabModifier extends Modifier {
element = null;
@ -50,10 +50,17 @@ export default class TrapTabModifier extends Modifier {
}
const focusableElements = FOCUSABLE_ELEMENTS + ", button:enabled";
const firstFocusableElement = this.element.querySelector(focusableElements);
const focusableContent = this.element.querySelectorAll(focusableElements);
const lastFocusableElement = focusableContent[focusableContent.length - 1];
const filteredFocusableElements = Array.from(
this.element.querySelectorAll(focusableElements)
).filter((element) => {
const tabindex = element.getAttribute("tabindex");
return tabindex !== "-1";
});
const firstFocusableElement = filteredFocusableElements[0];
const lastFocusableElement =
filteredFocusableElements[filteredFocusableElements.length - 1];
if (event.shiftKey) {
if (document.activeElement === firstFocusableElement) {
@ -63,7 +70,6 @@ export default class TrapTabModifier extends Modifier {
} else {
if (document.activeElement === lastFocusableElement) {
event.preventDefault();
(
this.element.querySelector(".modal-close") || firstFocusableElement
)?.focus({ preventScroll: this.preventScroll });

View File

@ -2132,6 +2132,9 @@ en:
private_message: "message"
the_topic: "the topic"
card: "User card"
profile_link: "%{username}, visit profile"
user_status:
save: "Save"
set_custom_status: "Set custom status"