aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMurtaza Sultani <sultani@data-quest.de>2026-01-19 12:28:04 +0100
committerJan-Hendrik Willms <tleilax+studip@gmail.com>2026-02-27 15:31:56 +0100
commite4f38e0e8cdc2cdd1dce822ca62260df3f588446 (patch)
tree38706b5106a1b579dbfd92f75500a7782134d446
parent04b813348082ef0dbf883d3b6a5b63077331eda6 (diff)
Resolve "Avatar-Menü: Barrierefreiheitsprobleme beheben"
Closes #6168 Merge request studip/studip!4673
-rw-r--r--resources/vue/components/Dropdown.vue6
-rw-r--r--resources/vue/components/UserAvatar.vue10
-rw-r--r--resources/vue/components/forum/ForumMembers.vue11
-rw-r--r--resources/vue/components/forum/SubscriptionDropdown.vue4
-rw-r--r--resources/vue/components/forum/UserAvatarDropdown.vue14
5 files changed, 32 insertions, 13 deletions
diff --git a/resources/vue/components/Dropdown.vue b/resources/vue/components/Dropdown.vue
index c02f196..2fb2a24 100644
--- a/resources/vue/components/Dropdown.vue
+++ b/resources/vue/components/Dropdown.vue
@@ -60,8 +60,8 @@ onBeforeUnmount(() => {
v-bind="$attrs"
ref="dropdown"
class="dropdown"
- aria-haspopup="true"
- :aria-expanded="isOpen.toString()"
+ aria-haspopup="menu"
+ :aria-expanded="isOpen"
>
<div ref="trigger">
<slot name="trigger">
@@ -73,7 +73,7 @@ onBeforeUnmount(() => {
v-if="isOpen"
ref="dropdownContent"
class="dropdown__content"
- aria-labelledby="dropdown-title"
+ :aria-labelledby="title ? 'dropdown-title' : null"
>
<button
type="button"
diff --git a/resources/vue/components/UserAvatar.vue b/resources/vue/components/UserAvatar.vue
index 441a41c..fee7f05 100644
--- a/resources/vue/components/UserAvatar.vue
+++ b/resources/vue/components/UserAvatar.vue
@@ -41,7 +41,7 @@ const openBlubberChat = () => {
}
</script>
<template>
- <div class="user-avatar">
+ <div class="user-avatar" v-bind="$attrs">
<div class="user-avatar__header">
<img class="user-profile" :src="user.avatar_url" :alt="user.name" />
<div class="user-info">
@@ -50,9 +50,11 @@ const openBlubberChat = () => {
</div>
</div>
<hr />
- <ul class="user-avatar__actions">
+ <ul class="user-avatar__actions" role="menu">
<li>
<button
+ role="menuitem"
+ type="button"
v-if="user.id !== AUTH_ID"
@click="openBlubberChat"
class="action-item"
@@ -65,6 +67,7 @@ const openBlubberChat = () => {
</li>
<li>
<a
+ role="menuitem"
class="action-item"
:href="userProfileURL"
:title="$gettext('Zum Profil von %{name}', { name: user.name })"
@@ -76,6 +79,8 @@ const openBlubberChat = () => {
</li>
<li>
<button
+ role="menuitem"
+ type="button"
v-if="user.id !== AUTH_ID"
class="action-item"
:title="$gettext('Nachricht schreiben')"
@@ -88,6 +93,7 @@ const openBlubberChat = () => {
</li>
<li>
<a
+ role="menuitem"
class="action-item"
:href="vCardDownloadURL"
:title="$gettext('vCard herunterladen')"
diff --git a/resources/vue/components/forum/ForumMembers.vue b/resources/vue/components/forum/ForumMembers.vue
index 54d29c4..e24442b 100644
--- a/resources/vue/components/forum/ForumMembers.vue
+++ b/resources/vue/components/forum/ForumMembers.vue
@@ -52,6 +52,8 @@ const isModerator = role => role === 'moderator';
@click="showAllMembers = !showAllMembers"
:title="$gettext('Alle Teilnehmende anzeigen')"
:aria-label="$gettext('Alle Teilnehmende anzeigen')"
+ aria-haspopup="menu"
+ :aria-expanded="showAllMembers"
>
<span class="remained-users__count" :style="{ width: size, height: size }">
+{{ remainedMembersCount }}
@@ -78,6 +80,8 @@ const isModerator = role => role === 'moderator';
@click="activeUserAvatar = user.id"
:title="$gettext('Aufklappen')"
:aria-label="$gettext('Aufklappen')"
+ :aria-expanded="activeUserAvatar === user.id"
+ :aria-controls="'user-avatar-' + user.id"
class="show-avatar button-base">
<StudipIcon shape="arr_1down" :size="15" aria-hidden="true" />
</button>
@@ -91,7 +95,8 @@ const isModerator = role => role === 'moderator';
class="hide-avatar button-base">
<StudipIcon shape="arr_1up" :size="15" aria-hidden="true" />
</button>
- <UserAvatar v-if="activeUserAvatar === user.id" :user="user" />
+
+ <UserAvatar v-if="activeUserAvatar === user.id" :id="'user-avatar-' + user.id" :user="user" />
</li>
</ul>
</div>
@@ -122,10 +127,12 @@ const isModerator = role => role === 'moderator';
@click="activeUserAvatar = ''"
:title="$gettext('Zuklappen')"
:aria-label="$gettext('Zuklappen')"
+ :aria-expanded="activeUserAvatar === user.id"
+ :aria-controls="'user-avatar-' + user.id"
class="hide-avatar button-base">
<StudipIcon shape="arr_1up" :size="15" aria-hidden="true" />
</button>
- <UserAvatar v-if="activeUserAvatar === user.id" :user="user" />
+ <UserAvatar v-if="activeUserAvatar === user.id" :id="'user-avatar-' + user.id" :user="user" />
</li>
</ul>
</div>
diff --git a/resources/vue/components/forum/SubscriptionDropdown.vue b/resources/vue/components/forum/SubscriptionDropdown.vue
index 4a569f0..75cfaad 100644
--- a/resources/vue/components/forum/SubscriptionDropdown.vue
+++ b/resources/vue/components/forum/SubscriptionDropdown.vue
@@ -155,6 +155,7 @@ const subscribe = async (notificationType = 'all') => {
<template #items>
<li>
<button
+ role="menuitem"
type="button"
:class="{
'active': subscription?.notification_type === SubscriptionNotificationType.All
@@ -174,6 +175,7 @@ const subscribe = async (notificationType = 'all') => {
</li>
<li>
<button
+ role="menuitem"
type="button"
:class="{
'active': subscription?.notification_type === SubscriptionNotificationType.RepliesOnly
@@ -193,6 +195,7 @@ const subscribe = async (notificationType = 'all') => {
</li>
<li>
<button
+ role="menuitem"
type="button"
:class="{
'active': subscription?.notification_type === SubscriptionNotificationType.None
@@ -212,6 +215,7 @@ const subscribe = async (notificationType = 'all') => {
</li>
<li>
<button
+ role="menuitem"
type="button"
:disabled="!subscription?.notification_type"
@click="unSubscribe"
diff --git a/resources/vue/components/forum/UserAvatarDropdown.vue b/resources/vue/components/forum/UserAvatarDropdown.vue
index 82f693d..7484b98 100644
--- a/resources/vue/components/forum/UserAvatarDropdown.vue
+++ b/resources/vue/components/forum/UserAvatarDropdown.vue
@@ -14,7 +14,7 @@ defineProps({
},
label: {
type: String,
- default: ''
+ default: null
}
});
@@ -24,22 +24,24 @@ const isOpen = defineModel({ default: false });
<Dropdown class="user-avatar-dropdown" v-model="isOpen">
<template #trigger>
<button
- class="user-avatar-dropdown__preview"
+ type="button"
+ class="user-avatar-dropdown__preview button-base"
@click="isOpen = !isOpen"
v-bind="$attrs"
:class="{
'active': isOpen
}"
- :title="label ?? user.name"
- :aria-label="label ?? $gettext('vCard')"
- :aria-pressed="isOpen"
+ :title="label ?? $gettext('Avatar-Menü öffnen')"
+ :aria-label="$gettext('Avatar-Menü für „%{ context }“ öffnen', { context: user.name })"
+ aria-haspopup="menu"
+ :aria-expanded="isOpen"
>
<img class="user-profile" :src="user.avatar_url" :style="{ width: size, height: size }" :alt="user.name" />
</button>
</template>
<template #content>
- <UserAvatar :user="user" v-model="isOpen" />
+ <UserAvatar :user="user" v-model="isOpen" :id="'user-avatar-' + user.id" />
</template>
</Dropdown>
</template>