Skip to content

Commit

Permalink
feat: ReadReceiptIndicator a11y improvement (#32317)
Browse files Browse the repository at this point in the history
Co-authored-by: Tiago Evangelista Pinto <17487063+tiagoevanp@users.noreply.github.com>
  • Loading branch information
dougfabris and tiagoevanp committed May 16, 2024
1 parent fb6d9eb commit b01cdce
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-berries-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---

Replace the read receipt receipt indicator in order to improve the accessibility complience
24 changes: 19 additions & 5 deletions apps/meteor/client/components/message/ReadReceiptIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { Box, Icon } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement } from 'react';
import React from 'react';

type ReadReceiptIndicatorProps = {
mid: IMessage['_id'];
unread?: boolean;
};

const ReadReceiptIndicator = ({ unread }: ReadReceiptIndicatorProps): ReactElement | null => (
<Box position='absolute' insetBlockStart={2} insetInlineEnd={8}>
<Icon name='check' size='x16' color={unread ? 'annotation' : 'info'} />
</Box>
);
const ReadReceiptIndicator = ({ mid, unread }: ReadReceiptIndicatorProps): ReactElement | null => {
const t = useTranslation();

return (
<Box
role='status'
id={`${mid}-read-status`}
aria-label={unread ? t('Message_sent') : t('Message_viewed')}
position='absolute'
insetBlockStart={2}
insetInlineEnd={8}
>
<Icon size='x16' name={unread ? 'check' : 'double-check'} color={unread ? 'annotation' : 'info'} />
</Box>
);
};

export default ReadReceiptIndicator;
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const RoomMessage = ({
role='listitem'
aria-roledescription={sequential ? t('sequential_message') : t('message')}
tabIndex={0}
aria-labelledby={`${message._id}-displayName ${message._id}-time ${message._id}-content`}
aria-labelledby={`${message._id}-displayName ${message._id}-time ${message._id}-content ${message._id}-read-status`}
onClick={selecting ? toggleSelected : undefined}
isSelected={selected}
isEditing={editing}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const RoomMessageContent = ({ message, unread, all, mention, searchText }: RoomM
<>
{(!encrypted || normalizedMessage.e2e === 'done') && (
<MessageContentBody
id={`${message._id}-content`}
id={`${normalizedMessage._id}-content`}
md={normalizedMessage.md}
mentions={normalizedMessage.mentions}
channels={normalizedMessage.channels}
Expand Down Expand Up @@ -111,7 +111,7 @@ const RoomMessageContent = ({ message, unread, all, mention, searchText }: RoomM
<BroadcastMetrics username={messageUser.username} message={normalizedMessage} />
)}

{readReceiptEnabled && <ReadReceiptIndicator unread={normalizedMessage.unread} />}
{readReceiptEnabled && <ReadReceiptIndicator mid={normalizedMessage._id} unread={normalizedMessage.unread} />}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem
<BroadcastMetrics username={messageUser.username} message={normalizedMessage} />
)}

{readReceiptEnabled && <ReadReceiptIndicator unread={normalizedMessage.unread} />}
{readReceiptEnabled && <ReadReceiptIndicator mid={normalizedMessage._id} unread={normalizedMessage.unread} />}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
import type { ReadReceipt } from '@rocket.chat/core-typings';
import { css } from '@rocket.chat/css-in-js';
import { Box, Palette } from '@rocket.chat/fuselage';
import { Box } from '@rocket.chat/fuselage';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import type { ReactElement } from 'react';
import React from 'react';

import { useFormatDateAndTime } from '../../../../hooks/useFormatDateAndTime';
import { useUserDisplayName } from '../../../../hooks/useUserDisplayName';

const hoverStyle = css`
&:hover {
background-color: ${Palette.surface['surface-neutral']};
}
`;

const ReadReceiptRow = ({ user, ts }: ReadReceipt): ReactElement => {
const displayName = useUserDisplayName(user || {});
const formatDateAndTime = useFormatDateAndTime({ withSeconds: true });

return (
<Box
display='flex'
flexDirection='row'
justifyContent='space-between'
alignItems='center'
p={4}
pi={32}
mi='neg-x32'
className={hoverStyle}
>
<Box role='listitem' display='flex' flexDirection='row' justifyContent='space-between' alignItems='center' mbe={8}>
<Box>
<UserAvatar username={user?.username || ''} size='x24' />
<Box is='span' mis={8}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ const ReadReceiptsModal = ({ messageId, onClose }: ReadReceiptsModalProps): Reac
return (
<GenericModal title={t('Read_by')} onConfirm={onClose} onClose={onClose}>
{readReceipts.length < 1 && t('No_results_found')}
{readReceipts.map((receipt) => (
<ReadReceiptRow {...receipt} key={receipt._id} />
))}
{readReceipts.length > 0 && (
<div role='list'>
{readReceipts.map((receipt) => (
<ReadReceiptRow {...receipt} key={receipt._id} />
))}
</div>
)}
</GenericModal>
);
};
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/ee/client/startup/readReceipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Meteor.startup(() => {

MessageAction.addButton({
id: 'receipt-detail',
icon: 'info-circled',
label: 'Info',
icon: 'double-check',
label: 'Read_Receipts',
context: ['starred', 'message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'],
type: 'duplication',
action(_, props) {
Expand Down
51 changes: 51 additions & 0 deletions apps/meteor/tests/e2e/read-receipts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { IS_EE } from './config/constants';
import { createAuxContext } from './fixtures/createAuxContext';
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { createTargetChannel, setSettingValueById } from './utils';
import { expect, test } from './utils/test';

test.use({ storageState: Users.admin.state });

test.describe.serial('read-receipts', () => {
let poHomeChannel: HomeChannel;
let targetChannel: string;

test.skip(!IS_EE, 'Enterprise Only');

test.beforeAll(async ({ api }) => {
await setSettingValueById(api, 'Message_Read_Receipt_Enabled', true);
await setSettingValueById(api, 'Message_Read_Receipt_Store_Users', true);

targetChannel = await createTargetChannel(api);
});

test.beforeEach(async ({ page }) => {
poHomeChannel = new HomeChannel(page);

await page.goto('/home');
});

test('should show read receipts message sent status in the sent message', async ({ browser }) => {
const { page } = await createAuxContext(browser, Users.user1);
const auxContext = { page, poHomeChannel: new HomeChannel(page) };
await auxContext.poHomeChannel.sidenav.openChat(targetChannel);
await auxContext.poHomeChannel.content.sendMessage('hello admin');

await expect(auxContext.poHomeChannel.content.lastUserMessage.getByRole('status', { name: 'Message sent' })).toBeVisible();
await auxContext.page.close();
});

test('should show read receipts message viewed status in the sent message', async () => {
await poHomeChannel.sidenav.openChat(targetChannel);
await expect(poHomeChannel.content.lastUserMessage.getByRole('status', { name: 'Message viewed' })).toBeVisible();
});

test('should show the reads receipt modal with the users who read the message', async ({ page }) => {
await poHomeChannel.sidenav.openChat(targetChannel);
await poHomeChannel.content.openLastMessageMenu();
await page.locator('role=menuitem[name="Read receipts"]').click();

await expect(page.getByRole('dialog').getByRole('listitem')).toHaveCount(2);
});
});
1 change: 1 addition & 0 deletions apps/meteor/tests/e2e/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './create-target-channel';
export * from './setSettingValueById';
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -3619,6 +3619,8 @@
"Messages": "Messages",
"Messages_selected": "Messages selected",
"Messages_sent": "Messages sent",
"Message_sent": "Message sent",
"Message_viewed": "Message viewed",
"Messages_that_are_sent_to_the_Incoming_WebHook_will_be_posted_here": "Messages that are sent to the Incoming WebHook will be posted here.",
"Meta": "Meta",
"Meta_Description": "Set custom Meta properties.",
Expand Down

0 comments on commit b01cdce

Please sign in to comment.