From 2255929413a125969d51418bc72e9d3de26e832a Mon Sep 17 00:00:00 2001 From: WofWca Date: Wed, 23 Apr 2025 20:05:22 +0400 Subject: [PATCH 1/4] improvement: add `aria-label(ledby)` for chat list So that screen readers say "Chats list" instead of just "list" when the focus enters such a list. --- .../frontend/src/components/chat/ChatList.tsx | 69 +++++++++++++++++-- .../components/dialogs/SelectChat/index.tsx | 3 + .../src/components/dialogs/ViewGroup.tsx | 10 ++- .../components/dialogs/ViewProfile/index.tsx | 7 +- 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/chat/ChatList.tsx b/packages/frontend/src/components/chat/ChatList.tsx index 7ed0e8d73a..30c6aedcbb 100644 --- a/packages/frontend/src/components/chat/ChatList.tsx +++ b/packages/frontend/src/components/chat/ChatList.tsx @@ -5,6 +5,8 @@ import React, { useCallback, ComponentType, useMemo, + HTMLAttributes, + useLayoutEffect, } from 'react' import { FixedSizeList as List, @@ -60,6 +62,7 @@ export function ChatListPart({ height, itemKey, setListRef, + olElementAttrs, itemData, itemHeight, }: { @@ -71,6 +74,10 @@ export function ChatListPart({ height: number itemKey: ListItemKeySelector setListRef?: (ref: List | null) => void + /** + * This does _not_ support maps with dynamically added/removed keys. + */ + olElementAttrs?: HTMLAttributes itemData: ChatListItemData | ContactChatListItemData | MessageChatListItemData itemHeight: number }) { @@ -92,6 +99,26 @@ export function ChatListPart({ // So let's play it safe. }) + const olRef = useRef(null) + // 'react-window' does not expose API to set attributes on its element, + // so we have to `useLayoutEffect`. + useLayoutEffect(() => { + if (olRef.current == null) { + return + } + if (olElementAttrs == undefined) { + return + } + + for (const [key, value] of Object.entries(olElementAttrs)) { + if (value == undefined) { + olRef.current.removeAttribute(key) + } else { + olRef.current.setAttribute(key, value) + } + } + }) + return ( ( {({ height }) => (
-
+
{tx('search_in', searchChatInfo.name)} {messageResultIds.length !== 0 && ': ' + translate_n('n_messages', messageResultIds.length)} @@ -378,6 +409,9 @@ export default function ChatList(props: { classNameOfTargetElements={rovingTabindexItemsClassName} > ( <> {isSearchActive && ( -
+
{translate_n('n_chats', chatListIds.length)}
)} @@ -419,6 +456,18 @@ export default function ChatList(props: { >
{isSearchActive && ( <> -
+
{translate_n('n_contacts', contactIds.length)}
-
+
{translated_messages_label(messageResultIds.length)}
@@ -483,6 +541,9 @@ export default function ChatList(props: { >
{({ height }) => ( {isRelatedChatsEnabled && ( <> -
{tx('related_chats')}
+
-
{tx('profile_shared_chats')}
+
+ {tx('profile_shared_chats')} +
{({ height }) => ( Date: Thu, 24 Apr 2025 00:24:58 +0400 Subject: [PATCH 2/4] improvement: a11y: `aria-label` for messages list So that screen readers announce "messages list" instead of just "list" when you focus an item inside of it. --- packages/frontend/src/components/message/MessageList.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/message/MessageList.tsx b/packages/frontend/src/components/message/MessageList.tsx index 51266f9a2b..d7c8928d30 100644 --- a/packages/frontend/src/components/message/MessageList.tsx +++ b/packages/frontend/src/components/message/MessageList.tsx @@ -732,6 +732,8 @@ export const MessageListInner = React.memo( unreadMessageInViewIntersectionObserver: React.MutableRefObject loadMissingMessages: () => Promise }) => { + const tx = useTranslationFunction() + const { onScroll, onScrollEnd, @@ -859,14 +861,14 @@ export const MessageListInner = React.memo( if (!loaded) { return (
-
    +
      ) } return (
      -
        +
          {messageListItems.length === 0 && } {activeView.map(messageId => { From a17896db156242e70a8556b2d17203cad74304ec Mon Sep 17 00:00:00 2001 From: WofWca Date: Thu, 24 Apr 2025 00:43:51 +0400 Subject: [PATCH 3/4] improvement: a11y: `aria-label` for accounts list --- .../AccountListSidebar/AccountItem.tsx | 130 ++++++++++-------- .../AccountListSidebar/AccountListSidebar.tsx | 14 +- .../AccountListSidebar/styles.module.scss | 43 +++--- 3 files changed, 105 insertions(+), 82 deletions(-) diff --git a/packages/frontend/src/components/AccountListSidebar/AccountItem.tsx b/packages/frontend/src/components/AccountListSidebar/AccountItem.tsx index 8ec5323f85..1df31ee8df 100644 --- a/packages/frontend/src/components/AccountListSidebar/AccountItem.tsx +++ b/packages/frontend/src/components/AccountListSidebar/AccountItem.tsx @@ -242,70 +242,78 @@ export default function AccountItem({ // for a different account, and upon initial render. return ( - + + ) } diff --git a/packages/frontend/src/components/AccountListSidebar/AccountListSidebar.tsx b/packages/frontend/src/components/AccountListSidebar/AccountListSidebar.tsx index 30c46a8539..acf7b6bab5 100644 --- a/packages/frontend/src/components/AccountListSidebar/AccountListSidebar.tsx +++ b/packages/frontend/src/components/AccountListSidebar/AccountListSidebar.tsx @@ -46,7 +46,7 @@ export default function AccountListSidebar({ }: Props) { const tx = useTranslationFunction() - const accountsListRef = useRef(null) + const accountsListRef = useRef(null) const { openDialog } = useDialog() const [accounts, setAccounts] = useState([]) const [{ accounts: noficationSettings }] = useAccountNotificationStore() @@ -144,8 +144,12 @@ export default function AccountListSidebar({ data-tauri-drag-region /> )} -
          ))} - +
        1. + +
        2. -
          + {/* The condition is the same as in https://github.com/deltachat/deltachat-desktop/blob/63af023437ff1828a27de2da37bf94ab180ec528/src/renderer/contexts/KeybindingsContext.tsx#L26 */} {window.__screen === Screens.Main && (
          diff --git a/packages/frontend/src/components/AccountListSidebar/styles.module.scss b/packages/frontend/src/components/AccountListSidebar/styles.module.scss index 249c00174d..22d77a81e9 100644 --- a/packages/frontend/src/components/AccountListSidebar/styles.module.scss +++ b/packages/frontend/src/components/AccountListSidebar/styles.module.scss @@ -50,6 +50,10 @@ --als-avatar-margin: 5px; --als-active-indicator-color: white; + margin: 0; + padding: 0; + list-style: none; + align-items: center; display: flex; flex-direction: column; @@ -63,6 +67,28 @@ } } + .accountWrapper { + margin-bottom: var(--als-avatar-margin); + margin-top: var(--als-avatar-margin); + + &.isSticky { + position: sticky; + bottom: var(--als-avatar-margin); + top: var(--als-avatar-margin); + // Only needed when this account is scrolled _above_ the visible region. + z-index: map-get($z-index, account-list-sidebar-scope-sticky-account); + + .account { + .avatar { + opacity: 1; + .content { + box-shadow: 0px 0px 4px 2px #00000040; + } + } + } + } + } + .account { border: none; background: none; @@ -71,8 +97,6 @@ height: var(--als-avatar-size); - margin-bottom: var(--als-avatar-margin); - margin-top: var(--als-avatar-margin); // Adding extra `var(--als-avatar-size) + 2 * var(--als-avatar-margin)` // to the margins so that // if there is another `.isSticky` account, then that account does not @@ -107,21 +131,6 @@ } } - &.isSticky { - position: sticky; - bottom: var(--als-avatar-margin); - top: var(--als-avatar-margin); - // Only needed when this account is scrolled _above_ the visible region. - z-index: map-get($z-index, account-list-sidebar-scope-sticky-account); - - .avatar { - opacity: 1; - .content { - box-shadow: 0px 0px 4px 2px #00000040; - } - } - } - &.active, &:hover, &.context-menu-active { From eea0295beefbb6355d2e5c9a0c5c931eb7865b17 Mon Sep 17 00:00:00 2001 From: WofWca Date: Sun, 4 May 2025 16:18:03 +0400 Subject: [PATCH 4/4] improvement: a11y: `aria-label` for contact lists --- .../src/components/contact/ContactList.tsx | 7 ++++++- .../components/dialogs/CreateChat/index.tsx | 13 +++++++++++-- .../src/components/dialogs/ViewGroup.tsx | 18 ++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/components/contact/ContactList.tsx b/packages/frontend/src/components/contact/ContactList.tsx index 2b82417ba5..ba2a0d5d3f 100644 --- a/packages/frontend/src/components/contact/ContactList.tsx +++ b/packages/frontend/src/components/contact/ContactList.tsx @@ -21,6 +21,7 @@ export function ContactList(props: { onRemoveClick?: (contact: Type.Contact) => void disabledContacts?: number[] onContactContextMenu?: (contact: Type.Contact) => void + olElementAttrs?: Omit, 'style'> }) { const { contacts, @@ -32,9 +33,13 @@ export function ContactList(props: { onRemoveClick, disabledContacts, onContactContextMenu, + olElementAttrs, } = props return ( -
            +
              {contacts.map(contact => { let checked = false if (showCheckbox && typeof isChecked === 'function') { diff --git a/packages/frontend/src/components/dialogs/CreateChat/index.tsx b/packages/frontend/src/components/dialogs/CreateChat/index.tsx index 3f33dceb08..8ce3deed21 100644 --- a/packages/frontend/src/components/dialogs/CreateChat/index.tsx +++ b/packages/frontend/src/components/dialogs/CreateChat/index.tsx @@ -565,7 +565,7 @@ export function CreateGroup(props: CreateGroupProps) { type='group' /> -
              +
              {tx('n_members', groupMembers.length.toString(), { quantity: groupMembers.length, })} @@ -587,6 +587,9 @@ export function CreateGroup(props: CreateGroupProps) { onRemoveClick={c => { removeGroupMember(c) }} + olElementAttrs={{ + 'aria-labelledby': 'create-group-members-title', + }} />
              @@ -693,7 +696,10 @@ function CreateBroadcastList(props: CreateBroadcastListProps) { />
              {broadcastRecipients.length > 0 && ( -
              +
              {tx('n_recipients', broadcastRecipients.length.toString(), { quantity: broadcastRecipients.length, })} @@ -716,6 +722,9 @@ function CreateBroadcastList(props: CreateBroadcastListProps) { onRemoveClick={c => { removeBroadcastRecipient(c) }} + olElementAttrs={{ + 'aria-labelledby': 'create-broadcast-list-recipients-title', + }} />
              diff --git a/packages/frontend/src/components/dialogs/ViewGroup.tsx b/packages/frontend/src/components/dialogs/ViewGroup.tsx index 0fd207c340..2847cde5b0 100644 --- a/packages/frontend/src/components/dialogs/ViewGroup.tsx +++ b/packages/frontend/src/components/dialogs/ViewGroup.tsx @@ -309,7 +309,10 @@ function ViewGroupInner(
              )} -
              +
              {!isBroadcast ? tx('n_members', groupMembers.length.toString(), { quantity: groupMembers.length, @@ -348,12 +351,20 @@ function ViewGroupInner( setProfileContact(contact) }} onRemoveClick={showRemoveGroupMemberConfirmationDialog} + olElementAttrs={{ + 'aria-labelledby': 'view-group-members-recipients-title', + }} />
              {pastContacts.length > 0 && ( <> -
              {tx('past_members')}
              +
              + {tx('past_members')} +