Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Correct accessibility labels for unread rooms in spotlight #9003

Merged
merged 14 commits into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ import { getDisplayAliasForRoom } from "../../../structures/RoomDirectory";
const MAX_NAME_LENGTH = 80;
const MAX_TOPIC_LENGTH = 800;

export function PublicRoomResultDetails({ room }: { room: IPublicRoomsChunkRoom }): JSX.Element {
interface Props {
room: IPublicRoomsChunkRoom;
labelId: string;
descriptionId: string;
detailsId: string;
}

export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
let name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
if (name.length > MAX_NAME_LENGTH) {
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
Expand All @@ -41,12 +48,12 @@ export function PublicRoomResultDetails({ room }: { room: IPublicRoomsChunkRoom
return (
<div className="mx_SpotlightDialog_result_publicRoomDetails">
<div className="mx_SpotlightDialog_result_publicRoomHeader">
<span className="mx_SpotlightDialog_result_publicRoomName">{ name }</span>
<span className="mx_SpotlightDialog_result_publicRoomAlias">
<span id={labelId} className="mx_SpotlightDialog_result_publicRoomName">{ name }</span>
<span id={descriptionId} className="mx_SpotlightDialog_result_publicRoomAlias">
{ room.canonical_alias ?? room.room_id }
</span>
</div>
<div className="mx_SpotlightDialog_result_publicRoomDescription">
<div id={detailsId} className="mx_SpotlightDialog_result_publicRoomDescription">
<span className="mx_SpotlightDialog_result_publicRoomMemberCount">
{ _t("%(count)s Members", {
count: room.num_joined_members,
Expand Down
25 changes: 22 additions & 3 deletions src/components/views/dialogs/spotlight/RoomResultDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,31 @@ limitations under the License.
import React from "react";
import { Room } from "matrix-js-sdk/src/matrix";

import { _t } from "../../../../languageHandler";
import { roomContextDetailsText, spaceContextDetailsText } from "../../../../utils/i18n-helpers";

export const RoomResultDetails = ({ room }: { room: Room }) => {
const contextDetails = room.isSpaceRoom() ? spaceContextDetailsText(room) : roomContextDetailsText(room);
interface Props {
id: string;
room: Room;
}

export const RoomResultDetails = ({ room, id }: Props) => {
let contextDetails: string;
let ariaLabel: string;
if (room.isSpaceRoom()) {
contextDetails = spaceContextDetailsText(room);
ariaLabel = _t("In space %(space)s.", {
justjanne marked this conversation as resolved.
Show resolved Hide resolved
space: contextDetails,
});
} else {
contextDetails = roomContextDetailsText(room);
}
if (contextDetails) {
return <div className="mx_SpotlightDialog_result_details">
return <div
id={id}
className="mx_SpotlightDialog_result_details"
aria-label={ariaLabel}
>
{ contextDetails }
</div>;
}
Expand Down
93 changes: 73 additions & 20 deletions src/components/views/dialogs/spotlight/SpotlightDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import SdkConfig from "../../../../SdkConfig";
import { SettingLevel } from "../../../../settings/SettingLevel";
import SettingsStore from "../../../../settings/SettingsStore";
import { BreadcrumbsStore } from "../../../../stores/BreadcrumbsStore";
import { RoomNotificationState } from "../../../../stores/notifications/RoomNotificationState";
import { RoomNotificationStateStore } from "../../../../stores/notifications/RoomNotificationStateStore";
import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import { RoomViewStore } from "../../../../stores/RoomViewStore";
Expand Down Expand Up @@ -259,6 +260,22 @@ const findVisibleRoomMembers = (cli: MatrixClient, filterDMs = true) => {
).filter(it => it.userId !== cli.getUserId());
};

const roomAriaLabel = (room: Room, notification: RoomNotificationState): string => {
if (notification.hasMentions) {
return `${room.name} ${_t("%(count)s unread messages including mentions.", {
count: notification.count,
})}`;
} else if (notification.hasUnreadCount) {
return `${room.name} ${_t("%(count)s unread messages.", {
count: notification.count,
})}`;
} else if (notification.isUnread) {
return `${room.name} ${_t("Unread messages.")}`;
} else {
return room.name;
}
};

interface IDirectoryOpts {
limit: number;
query: string;
Expand Down Expand Up @@ -523,22 +540,33 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
if (trimmedQuery || filter !== null) {
const resultMapper = (result: Result): JSX.Element => {
if (isRoomResult(result)) {
const notification = RoomNotificationStateStore.instance.getRoomState(result.room);
return (
<Option
id={`mx_SpotlightDialog_button_result_${result.room.roomId}`}
key={`${Section[result.section]}-${result.room.roomId}`}
onClick={(ev) => {
viewRoom(result.room.roomId, true, ev?.type !== "click");
}}
aria-label={roomAriaLabel(result.room, notification)}
aria-describedby={`mx_SpotlightDialog_button_result_${result.room.roomId}_details`}
>
<DecoratedRoomAvatar room={result.room} avatarSize={AVATAR_SIZE} tooltipProps={{ tabIndex: -1 }} />
<DecoratedRoomAvatar
room={result.room}
avatarSize={AVATAR_SIZE}
tooltipProps={{ tabIndex: -1 }}
/>
{ result.room.name }
<NotificationBadge notification={RoomNotificationStateStore.instance.getRoomState(result.room)} />
<RoomResultDetails room={result.room} />
<NotificationBadge notification={notification} />
<RoomResultDetails
id={`mx_SpotlightDialog_button_result_${result.room.roomId}_details`}
room={result.room}
/>
</Option>
);
}
if (isMemberResult(result)) {
const label = result.member instanceof RoomMember ? result.member.rawDisplayName : result.member.name;
return (
<Option
id={`mx_SpotlightDialog_button_result_${result.member.userId}`}
Expand All @@ -547,10 +575,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
startDm(cli, [result.member]);
onFinished();
}}
aria-label={label}
aria-describedby={`mx_SpotlightDialog_button_result_${result.member.userId}_details`}
>
<SearchResultAvatar user={result.member} size={AVATAR_SIZE} />
{ result.member instanceof RoomMember ? result.member.rawDisplayName : result.member.name }
<div className="mx_SpotlightDialog_result_details">
{ label }
<div
id={`mx_SpotlightDialog_button_result_${result.member.userId}_details`}
className="mx_SpotlightDialog_result_details"
>
{ result.member.userId }
</div>
</Option>
Expand All @@ -575,6 +608,9 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
>
{ _t(clientRoom ? "View" : "Join") }
</AccessibleButton>}
aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`}
aria-describedby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_alias`}
aria-details={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_details`}
>
<BaseAvatar
className="mx_SearchResultAvatar"
Expand All @@ -586,7 +622,12 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
width={AVATAR_SIZE}
height={AVATAR_SIZE}
/>
<PublicRoomResultDetails room={result.publicRoom} />
<PublicRoomResultDetails
room={result.publicRoom}
labelId={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`}
descriptionId={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_alias`}
detailsId={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_details`}
/>
</Option>
);
}
Expand Down Expand Up @@ -867,20 +908,32 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
</AccessibleButton>
</h4>
<div>
{ recentSearches.map(room => (
<Option
id={`mx_SpotlightDialog_button_recentSearch_${room.roomId}`}
key={room.roomId}
onClick={(ev) => {
viewRoom(room.roomId, true, ev?.type !== "click");
}}
>
<DecoratedRoomAvatar room={room} avatarSize={AVATAR_SIZE} tooltipProps={{ tabIndex: -1 }} />
{ room.name }
<NotificationBadge notification={RoomNotificationStateStore.instance.getRoomState(room)} />
<RoomResultDetails room={room} />
</Option>
)) }
{ recentSearches.map(room => {
const notification = RoomNotificationStateStore.instance.getRoomState(room);
return (
<Option
id={`mx_SpotlightDialog_button_recentSearch_${room.roomId}`}
key={room.roomId}
onClick={(ev) => {
viewRoom(room.roomId, true, ev?.type !== "click");
}}
aria-label={roomAriaLabel(room, notification)}
aria-describedby={`mx_SpotlightDialog_button_result_${room.roomId}_details`}
>
<DecoratedRoomAvatar
room={room}
avatarSize={AVATAR_SIZE}
tooltipProps={{ tabIndex: -1 }}
/>
{ room.name }
<NotificationBadge notification={notification} />
<RoomResultDetails
id={`mx_SpotlightDialog_button_result_${room.roomId}_details`}
room={room}
/>
</Option>
);
}) }
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -2790,6 +2790,7 @@
"Remember this": "Remember this",
"%(count)s Members|other": "%(count)s Members",
"%(count)s Members|one": "%(count)s Member",
"In space %(space)s.": "In space %(space)s.",
"Public rooms": "Public rooms",
"Use \"%(query)s\" to search": "Use \"%(query)s\" to search",
"Search for": "Search for",
Expand Down