From dac42e062c62ad385be0f7c2f223cdfb6edc378c Mon Sep 17 00:00:00 2001 From: jyn <github@jyn.dev> Date: Mon, 21 Oct 2024 05:00:51 -0400 Subject: [PATCH] feat(a11y): make menu buttons in 'More' dropdown selectable with the keyboard (#2976) Co-authored-by: userquin <userquin@gmail.com> --- components/account/AccountMoreButton.vue | 14 ++++++++++++ components/common/dropdown/DropdownItem.vue | 25 ++++++++++++++------- components/status/StatusActionsMore.vue | 21 +++++++++++++++++ 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/components/account/AccountMoreButton.vue b/components/account/AccountMoreButton.vue index 7b2fe672..e7b5d089 100644 --- a/components/account/AccountMoreButton.vue +++ b/components/account/AccountMoreButton.vue @@ -71,6 +71,7 @@ async function removeUserNote() { /> </NuxtLink> <CommonDropdownItem + is="button" v-if="isShareSupported" :text="$t('menu.share_account', [`@${account.acct}`])" icon="i-ri:share-line" @@ -81,12 +82,14 @@ async function removeUserNote() { <template v-if="currentUser"> <template v-if="!isSelf"> <CommonDropdownItem + is="button" :text="$t('menu.mention_account', [`@${account.acct}`])" icon="i-ri:at-line" :command="command" @click="mentionUser(account)" /> <CommonDropdownItem + is="button" :text="$t('menu.direct_message_account', [`@${account.acct}`])" icon="i-ri:message-3-line" :command="command" @@ -94,6 +97,7 @@ async function removeUserNote() { /> <CommonDropdownItem + is="button" v-if="!relationship?.showingReblogs" icon="i-ri:repeat-line" :text="$t('menu.show_reblogs', [`@${account.acct}`])" @@ -101,6 +105,7 @@ async function removeUserNote() { @click="toggleReblogs()" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.hide_reblogs', [`@${account.acct}`])" icon="i-ri:repeat-line" @@ -109,6 +114,7 @@ async function removeUserNote() { /> <CommonDropdownItem + is="button" v-if="!relationship?.note || relationship?.note?.length === 0" :text="$t('menu.add_personal_note', [`@${account.acct}`])" icon="i-ri-edit-2-line" @@ -116,6 +122,7 @@ async function removeUserNote() { @click="addUserNote()" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.remove_personal_note', [`@${account.acct}`])" icon="i-ri-edit-2-line" @@ -124,6 +131,7 @@ async function removeUserNote() { /> <CommonDropdownItem + is="button" v-if="!relationship?.muting" :text="$t('menu.mute_account', [`@${account.acct}`])" icon="i-ri:volume-mute-line" @@ -131,6 +139,7 @@ async function removeUserNote() { @click="toggleMuteAccount (relationship!, account)" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.unmute_account', [`@${account.acct}`])" icon="i-ri:volume-up-fill" @@ -139,6 +148,7 @@ async function removeUserNote() { /> <CommonDropdownItem + is="button" v-if="!relationship?.blocking" :text="$t('menu.block_account', [`@${account.acct}`])" icon="i-ri:forbid-2-line" @@ -146,6 +156,7 @@ async function removeUserNote() { @click="toggleBlockAccount (relationship!, account)" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.unblock_account', [`@${account.acct}`])" icon="i-ri:checkbox-circle-line" @@ -155,6 +166,7 @@ async function removeUserNote() { <template v-if="getServerName(account) !== currentServer"> <CommonDropdownItem + is="button" v-if="!relationship?.domainBlocking" :text="$t('menu.block_domain', [getServerName(account)])" icon="i-ri:shut-down-line" @@ -162,6 +174,7 @@ async function removeUserNote() { @click="toggleBlockDomain(relationship!, account)" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.unblock_domain', [getServerName(account)])" icon="i-ri:restart-line" @@ -171,6 +184,7 @@ async function removeUserNote() { </template> <CommonDropdownItem + is="button" :text="$t('menu.report_account', [`@${account.acct}`])" icon="i-ri:flag-2-line" :command="command" diff --git a/components/common/dropdown/DropdownItem.vue b/components/common/dropdown/DropdownItem.vue index 2a1abc33..525e71b7 100644 --- a/components/common/dropdown/DropdownItem.vue +++ b/components/common/dropdown/DropdownItem.vue @@ -1,16 +1,24 @@ <script setup lang="ts"> -const props = withDefaults(defineProps<{ +const { + is = 'div', + text, + description, + icon, + checked, + command, +} = defineProps<{ is?: string text?: string description?: string icon?: string checked?: boolean command?: boolean -}>(), { - is: 'div', -}) +}>() + const emit = defineEmits(['click']) +const type = computed(() => is === 'button' ? 'button' : null) + const { hide } = useDropdownContext() || {} const el = ref<HTMLDivElement>() @@ -24,11 +32,11 @@ useCommand({ scope: 'Actions', order: -1, - visible: () => props.command && props.text, + visible: () => command && text, - name: () => props.text!, - icon: () => props.icon ?? 'i-ri:question-line', - description: () => props.description, + name: () => text!, + icon: () => icon ?? 'i-ri:question-line', + description: () => description, onActivate() { const clickEvent = new MouseEvent('click', { @@ -46,6 +54,7 @@ useCommand({ v-bind="$attrs" :is="is" ref="el" + :type="type" w-full flex gap-3 items-center cursor-pointer px4 py3 select-none diff --git a/components/status/StatusActionsMore.vue b/components/status/StatusActionsMore.vue index e6f1be5a..e4f47ce3 100644 --- a/components/status/StatusActionsMore.vue +++ b/components/status/StatusActionsMore.vue @@ -146,6 +146,7 @@ function showFavoritedAndBoostedBy() { <div flex="~ col"> <template v-if="getPreferences(userSettings, 'zenMode') && !details"> <CommonDropdownItem + is="button" :text="$t('action.reply')" icon="i-ri:chat-1-line" :command="command" @@ -153,6 +154,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="status.reblogged ? $t('action.boosted') : $t('action.boost')" icon="i-ri:repeat-fill" :class="status.reblogged ? 'text-green' : ''" @@ -162,6 +164,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="status.favourited ? $t('action.favourited') : $t('action.favourite')" :icon="useStarFavoriteIcon ? status.favourited ? 'i-ri:star-fill' : 'i-ri:star-line' @@ -176,6 +179,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="status.bookmarked ? $t('action.bookmarked') : $t('action.bookmark')" :icon="status.bookmarked ? 'i-ri:bookmark-fill' : 'i-ri:bookmark-line'" :class="status.bookmarked @@ -189,6 +193,7 @@ function showFavoritedAndBoostedBy() { </template> <CommonDropdownItem + is="button" :text="$t('menu.show_favourited_and_boosted_by')" icon="i-ri:hearts-line" :command="command" @@ -196,6 +201,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="$t('menu.copy_link_to_post')" icon="i-ri:link" :command="command" @@ -203,6 +209,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="$t('menu.copy_original_link_to_post')" icon="i-ri:links-fill" :command="command" @@ -210,6 +217,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" v-if="isShareSupported" :text="$t('menu.share_post')" icon="i-ri:share-line" @@ -218,6 +226,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" v-if="currentUser && (status.account.id === currentUser.account.id || status.mentions.some(m => m.id === currentUser!.account.id))" :text="status.muted ? $t('menu.unmute_conversation') : $t('menu.mute_conversation')" :icon="status.muted ? 'i-ri:eye-line' : 'i-ri:eye-off-line'" @@ -237,6 +246,7 @@ function showFavoritedAndBoostedBy() { <template v-if="isHydrated && currentUser"> <template v-if="isAuthor"> <CommonDropdownItem + is="button" :text="status.pinned ? $t('menu.unpin_on_profile') : $t('menu.pin_on_profile')" icon="i-ri:pushpin-line" :command="command" @@ -244,6 +254,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="$t('menu.edit')" icon="i-ri:edit-line" :command="command" @@ -251,6 +262,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="$t('menu.delete')" icon="i-ri:delete-bin-line" text-red-600 @@ -259,6 +271,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" :text="$t('menu.delete_and_redraft')" icon="i-ri:eraser-line" text-red-600 @@ -268,6 +281,7 @@ function showFavoritedAndBoostedBy() { </template> <template v-else> <CommonDropdownItem + is="button" :text="$t('menu.mention_account', [`@${status.account.acct}`])" icon="i-ri:at-line" :command="command" @@ -275,6 +289,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" v-if="!useRelationship(status.account).value?.muting" :text="$t('menu.mute_account', [`@${status.account.acct}`])" icon="i-ri:volume-mute-line" @@ -282,6 +297,7 @@ function showFavoritedAndBoostedBy() { @click="toggleMuteAccount(useRelationship(status.account).value!, status.account)" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.unmute_account', [`@${status.account.acct}`])" icon="i-ri:volume-up-fill" @@ -290,6 +306,7 @@ function showFavoritedAndBoostedBy() { /> <CommonDropdownItem + is="button" v-if="!useRelationship(status.account).value?.blocking" :text="$t('menu.block_account', [`@${status.account.acct}`])" icon="i-ri:forbid-2-line" @@ -297,6 +314,7 @@ function showFavoritedAndBoostedBy() { @click="toggleBlockAccount(useRelationship(status.account).value!, status.account)" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.unblock_account', [`@${status.account.acct}`])" icon="i-ri:checkbox-circle-line" @@ -306,6 +324,7 @@ function showFavoritedAndBoostedBy() { <template v-if="getServerName(status.account) && getServerName(status.account) !== currentServer"> <CommonDropdownItem + is="button" v-if="!useRelationship(status.account).value?.domainBlocking" :text="$t('menu.block_domain', [getServerName(status.account)])" icon="i-ri:shut-down-line" @@ -313,6 +332,7 @@ function showFavoritedAndBoostedBy() { @click="toggleBlockDomain(useRelationship(status.account).value!, status.account)" /> <CommonDropdownItem + is="button" v-else :text="$t('menu.unblock_domain', [getServerName(status.account)])" icon="i-ri:restart-line" @@ -322,6 +342,7 @@ function showFavoritedAndBoostedBy() { </template> <CommonDropdownItem + is="button" :text="$t('menu.report_account', [`@${status.account.acct}`])" icon="i-ri:flag-2-line" :command="command"