From 17c818409a5713e4f40eb5516250125c6bb39894 Mon Sep 17 00:00:00 2001 From: WofWca Date: Fri, 18 Oct 2024 20:29:43 +0400 Subject: [PATCH] improvement: arrow-key navigation for chat list Implements the "roving tabindex" approach: https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets#technique_1_roving_tabindex. Supersedes https://github.com/deltachat/deltachat-desktop/pull/4211. This improves things, but the UX as a whole is not great yet: - The tab order is such that the chat list does not immediately follow the search field, so you have to tab through the other navbar items before you get to the chat list. Same for going back from the chat list to the search bar. - The initially "active" element is just the first chat item, and not the currently selected chat. - Since the chat list is "virtualized", the currently active element might get removed from DOM when the user scrolls, thus we lose track of the item that was last selected. Related: https://github.com/deltachat/deltachat-desktop/issues/2784 --- CHANGELOG.md | 1 + .../frontend/src/components/chat/ChatList.tsx | 188 ++++++++------- .../src/components/chat/ChatListItem.tsx | 63 ++++- .../frontend/src/contexts/RovingTabindex.tsx | 219 ++++++++++++++++++ 4 files changed, 379 insertions(+), 92 deletions(-) create mode 100644 packages/frontend/src/contexts/RovingTabindex.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index ac7775d99a..243a3bf42e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased][unreleased] ## Added +- accessibility: arrow-key navigation for the list of chats #4224 - Add "Learn More" button to "Disappearing Messages" dialog #4330 - new icon for Mac users - smooth-scroll to newly arriving messages instead of jumping instantly #4125 diff --git a/packages/frontend/src/components/chat/ChatList.tsx b/packages/frontend/src/components/chat/ChatList.tsx index f081fbb4f8..c1eeb20b07 100644 --- a/packages/frontend/src/components/chat/ChatList.tsx +++ b/packages/frontend/src/components/chat/ChatList.tsx @@ -44,6 +44,7 @@ import type { MessageChatListItemData, } from './ChatListItemRow' import { isInviteLink } from '../../../../shared/util' +import { RovingTabindexProvider } from '../../contexts/RovingTabindex' const enum LoadStatus { FETCHING = 1, @@ -163,6 +164,8 @@ export default function ChatList(props: { const createChatByContactId = useCreateChatByContactId() const { selectChat } = useChat() + const tabindexWrapperElement = useRef(null) + const addContactOnClick = async () => { if (!queryStrIsValidEmail || !queryStr) return @@ -344,27 +347,31 @@ export default function ChatList(props: {
{({ width, height }) => ( -
+
{tx('search_in', searchChatInfo.name)} {messageResultIds.length !== 0 && ': ' + translate_n('n_messages', messageResultIds.length)}
- 'key' + messageResultIds[index]} - itemData={messagelistData} - itemHeight={CHATLISTITEM_MESSAGE_HEIGHT} + - {ChatListItemRowMessage} - + 'key' + messageResultIds[index]} + itemData={messagelistData} + itemHeight={CHATLISTITEM_MESSAGE_HEIGHT} + > + {ChatListItemRowMessage} + +
)} @@ -378,90 +385,97 @@ export default function ChatList(props: {
{({ width, height }) => ( -
+
{isSearchActive && (
{translate_n('n_chats', chatListIds.length)}
)} - | null) => - ((listRefRef.current as any) = ref) - } - itemKey={index => 'key' + chatListIds[index]} - itemData={chatlistData} - itemHeight={CHATLISTITEM_CHAT_HEIGHT} + {/* TODO RovingTabindex doesn't work well with virtualized + lists, because the currently active element might get removed + from DOM if scrolled out of view. */} + - {ChatListItemRowChat} - - {isSearchActive && ( - <> -
- {translate_n('n_contacts', contactIds.length)} -
- 'key' + contactIds[index]} - itemData={contactlistData} - itemHeight={CHATLISTITEM_CONTACT_HEIGHT} - > - {ChatListItemRowContact} - - {contactIds.length === 0 && - chatListIds.length === 0 && - queryStrIsValidEmail && ( + | null) => + ((listRefRef.current as any) = ref) + } + itemKey={index => 'key' + chatListIds[index]} + itemData={chatlistData} + itemHeight={CHATLISTITEM_CHAT_HEIGHT} + > + {ChatListItemRowChat} + + {isSearchActive && ( + <> +
+ {translate_n('n_contacts', contactIds.length)} +
+ 'key' + contactIds[index]} + itemData={contactlistData} + itemHeight={CHATLISTITEM_CONTACT_HEIGHT} + > + {ChatListItemRowContact} + + {contactIds.length === 0 && + chatListIds.length === 0 && + queryStrIsValidEmail && ( +
+ +
+ )} + {showPseudoListItemAddContactFromInviteLink && (
-
)} - {showPseudoListItemAddContactFromInviteLink && ( -
- +
+ {translated_messages_label(messageResultIds.length)}
- )} -
- {translated_messages_label(messageResultIds.length)} -
- 'key' + messageResultIds[index]} - itemData={messagelistData} - itemHeight={CHATLISTITEM_MESSAGE_HEIGHT} - > - {ChatListItemRowMessage} - - - )} + 'key' + messageResultIds[index]} + itemData={messagelistData} + itemHeight={CHATLISTITEM_MESSAGE_HEIGHT} + > + {ChatListItemRowMessage} + + + )} +