mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-05 12:16:53 +02:00
Improve voiceover experience
- As well as stylng cells, set the tabIndex(roving) - Natively focus the div with .focus() so screen reader actually moves over the cells - improve labels and roles
This commit is contained in:
parent
919b5ee452
commit
ce68db5c20
@ -117,10 +117,14 @@ const MemberListView: React.FC<IProps> = (props: IProps) => {
|
||||
<MemberListHeaderView vm={vm} />
|
||||
</Form.Root>
|
||||
<Virtuoso
|
||||
aria-label={_t("room_list|list_title")}
|
||||
role="grid"
|
||||
ref={ref}
|
||||
style={{ height: "100%" }}
|
||||
scrollerRef={scrollerRef}
|
||||
context={{ focusedIndex }}
|
||||
// Don't focus on the table as a whole go straight to the first item in the list
|
||||
tabIndex={undefined}
|
||||
data={vm.members}
|
||||
onFocus={onFocus}
|
||||
itemContent={(index, member) => getRowComponent(member, index === focusedIndex)}
|
||||
|
||||
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type JSX } from "react";
|
||||
import React, { useEffect, type JSX } from "react";
|
||||
|
||||
import DisambiguatedProfile from "../../../messages/DisambiguatedProfile";
|
||||
import { type RoomMember } from "../../../../../models/rooms/RoomMember";
|
||||
@ -37,7 +37,7 @@ export function RoomMemberTileView(props: IProps): JSX.Element {
|
||||
/>
|
||||
);
|
||||
const name = vm.name;
|
||||
const nameJSX = <DisambiguatedProfile member={member} fallbackName={name || ""} />;
|
||||
const nameJSX = <DisambiguatedProfile withTooltip member={member} fallbackName={name || ""} />;
|
||||
|
||||
const presenceState = member.presenceState;
|
||||
let presenceJSX: JSX.Element | undefined;
|
||||
@ -60,6 +60,7 @@ export function RoomMemberTileView(props: IProps): JSX.Element {
|
||||
presenceJsx={presenceJSX}
|
||||
nameJsx={nameJSX}
|
||||
userLabel={vm.userLabel}
|
||||
ariaLabel={_t("member_list|open_profile", { memberName: name })}
|
||||
iconJsx={iconJsx}
|
||||
focused={props.focused}
|
||||
/>
|
||||
|
||||
@ -12,6 +12,7 @@ import { type ThreePIDInvite } from "../../../../../models/rooms/ThreePIDInvite"
|
||||
import BaseAvatar from "../../../avatars/BaseAvatar";
|
||||
import { MemberTileView } from "./common/MemberTileView";
|
||||
import { InvitedIconView } from "./common/InvitedIconView";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
|
||||
interface Props {
|
||||
threePidInvite: ThreePIDInvite;
|
||||
@ -22,12 +23,14 @@ export function ThreePidInviteTileView(props: Props): JSX.Element {
|
||||
const vm = useThreePidTileViewModel(props);
|
||||
const av = <BaseAvatar name={vm.name} size="32px" aria-hidden="true" />;
|
||||
const iconJsx = <InvitedIconView isThreePid={true} />;
|
||||
const name = vm.name;
|
||||
|
||||
return (
|
||||
<MemberTileView
|
||||
nameJsx={vm.name}
|
||||
nameJsx={name}
|
||||
avatarJsx={av}
|
||||
onClick={vm.onClick}
|
||||
ariaLabel={_t("member_list|open_profile", { memberName: name })}
|
||||
userLabel={vm.userLabel}
|
||||
iconJsx={iconJsx}
|
||||
focused={props.focused}
|
||||
|
||||
@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { type JSX } from "react";
|
||||
import React, { useEffect, useRef, type JSX } from "react";
|
||||
|
||||
import AccessibleButton from "../../../../elements/AccessibleButton";
|
||||
|
||||
@ -14,6 +14,7 @@ interface Props {
|
||||
avatarJsx: JSX.Element;
|
||||
nameJsx: JSX.Element | string;
|
||||
onClick: () => void;
|
||||
ariaLabel: string;
|
||||
presenceJsx?: JSX.Element;
|
||||
userLabel?: React.ReactNode;
|
||||
iconJsx?: JSX.Element;
|
||||
@ -25,16 +26,24 @@ export function MemberTileView(props: Props): JSX.Element {
|
||||
if (props.userLabel) {
|
||||
userLabelJsx = <div className="mx_MemberTileView_userLabel">{props.userLabel}</div>;
|
||||
}
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
if (props.focused) {
|
||||
ref.current?.focus();
|
||||
}
|
||||
}, [props.focused]);
|
||||
return (
|
||||
// The wrapping div is required to make the magic mouse listener work, for some reason.
|
||||
<div>
|
||||
<AccessibleButton
|
||||
ref={ref}
|
||||
className={classNames("mx_MemberTileView", {
|
||||
mx_MemberTileView_hover: props.focused,
|
||||
})}
|
||||
onClick={props.onClick}
|
||||
tabIndex={-1}
|
||||
aria-label={props.ariaLabel}
|
||||
tabIndex={props.focused ? 0 : -1}
|
||||
role="gridcell"
|
||||
>
|
||||
<div className="mx_MemberTileView_left">
|
||||
<div className="mx_MemberTileView_avatar">
|
||||
|
||||
@ -1645,7 +1645,9 @@
|
||||
"invite_button_no_perms_tooltip": "You do not have permission to invite users",
|
||||
"invited_label": "Invited",
|
||||
"no_matches": "No matches",
|
||||
"power_label": "%(userName)s (power %(powerLevelNumber)s)"
|
||||
"power_label": "%(userName)s (power %(powerLevelNumber)s)",
|
||||
"list_title": "Member list",
|
||||
"open_profile": "Open profile %(memberName)s"
|
||||
},
|
||||
"member_list_back_action_label": "Room members",
|
||||
"message_edit_dialog_title": "Message edits",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user