Files
cinny/src/app/components/message/Reaction.tsx
T
jared cb3d2c40e5 fix(a11y): descriptive aria-label on reaction buttons (P3-4)
Reaction.tsx now computes aria-label='{shortcode} reaction, N people'
using getShortcodeFor so screen readers announce emoji name and count
instead of an ambiguous button. Custom (mxc://) emoji falls back to
'custom emoji reaction'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:50:19 -04:00

123 lines
3.3 KiB
TypeScript

import React from 'react';
import { Box, Text, as } from 'folds';
import classNames from 'classnames';
import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
import * as css from './Reaction.css';
import { getHexcodeForEmoji, getShortcodeFor } from '../../plugins/emoji';
import { getMemberDisplayName } from '../../utils/room';
import { eventWithShortcode, getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
export const Reaction = as<
'button',
{
mx: MatrixClient;
count: number;
reaction: string;
useAuthentication?: boolean;
}
>(({ className, mx, count, reaction, useAuthentication, ...props }, ref) => {
const shortcode = reaction.startsWith('mxc://')
? 'custom emoji'
: (getShortcodeFor(getHexcodeForEmoji(reaction)) ?? reaction);
const label = `${shortcode} reaction, ${count} ${count === 1 ? 'person' : 'people'}`;
return (
<Box
as="button"
className={classNames(css.Reaction, className)}
alignItems="Center"
shrink="No"
gap="200"
aria-label={label}
{...props}
ref={ref}
>
<Text className={css.ReactionText} as="span" size="T400">
{reaction.startsWith('mxc://') ? (
<img
className={css.ReactionImg}
src={mxcUrlToHttp(mx, reaction, useAuthentication) ?? reaction}
alt={reaction}
/>
) : (
<Text as="span" size="Inherit" truncate>
{reaction}
</Text>
)}
</Text>
<Text as="span" size="T300">
{count}
</Text>
</Box>
);
});
type ReactionTooltipMsgProps = {
room: Room;
reaction: string;
events: MatrixEvent[];
};
export function ReactionTooltipMsg({ room, reaction, events }: ReactionTooltipMsgProps) {
const shortCodeEvt = events.find(eventWithShortcode);
const shortcode =
shortCodeEvt?.getContent().shortcode ??
getShortcodeFor(getHexcodeForEmoji(reaction)) ??
reaction;
const names = events.map(
(ev: MatrixEvent) =>
getMemberDisplayName(room, ev.getSender() ?? 'Unknown') ??
getMxIdLocalPart(ev.getSender() ?? 'Unknown') ??
'Unknown',
);
return (
<>
{names.length === 1 && <b>{names[0]}</b>}
{names.length === 2 && (
<>
<b>{names[0]}</b>
<Text as="span" size="Inherit" priority="300">
{' and '}
</Text>
<b>{names[1]}</b>
</>
)}
{names.length === 3 && (
<>
<b>{names[0]}</b>
<Text as="span" size="Inherit" priority="300">
{', '}
</Text>
<b>{names[1]}</b>
<Text as="span" size="Inherit" priority="300">
{' and '}
</Text>
<b>{names[2]}</b>
</>
)}
{names.length > 3 && (
<>
<b>{names[0]}</b>
<Text as="span" size="Inherit" priority="300">
{', '}
</Text>
<b>{names[1]}</b>
<Text as="span" size="Inherit" priority="300">
{', '}
</Text>
<b>{names[2]}</b>
<Text as="span" size="Inherit" priority="300">
{' and '}
</Text>
<b>{names.length - 3} others</b>
</>
)}
<Text as="span" size="Inherit" priority="300">
{' reacted with '}
</Text>
:<b>{shortcode}</b>:
</>
);
}