cb3d2c40e5
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>
123 lines
3.3 KiB
TypeScript
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>:
|
|
</>
|
|
);
|
|
}
|