fix(a11y): stable reaction labels — emoji glyph, custom emoji shortcode (#179)
CI / Secret scan (gitleaks) (push) Successful in 52s
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Successful in 52s
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
The reaction button's aria-label used getShortcodeFor(), which returns undefined until the lazily loaded emoji data arrives. The same button read "🎉 reaction, 1 person" on first render and "tada reaction, 2 people" after any later re-render. It now always uses the emoji itself (screen readers speak it by its proper name, e.g. "party popper"). Custom (mxc) emoji were labelled just "custom emoji"; they now use the shortcode carried on the reaction event (":lotus_blob: reaction"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
52d94058cf
commit
1b5e6a37f5
@@ -13,13 +13,21 @@ export const Reaction = as<
|
|||||||
mx: MatrixClient;
|
mx: MatrixClient;
|
||||||
count: number;
|
count: number;
|
||||||
reaction: string;
|
reaction: string;
|
||||||
|
/** Custom-emoji shortcode from the reaction event, used for its label. */
|
||||||
|
shortcode?: string;
|
||||||
useAuthentication?: boolean;
|
useAuthentication?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, mx, count, reaction, useAuthentication, ...props }, ref) => {
|
>(({ className, mx, count, reaction, shortcode, useAuthentication, ...props }, ref) => {
|
||||||
const shortcode = reaction.startsWith('mxc://')
|
// Name a unicode reaction by the emoji itself: screen readers speak it by its
|
||||||
? 'custom emoji'
|
// proper name ("party popper"). Don't use getShortcodeFor here — it returns
|
||||||
: (getShortcodeFor(getHexcodeForEmoji(reaction)) ?? reaction);
|
// undefined until the lazy emoji data loads, so the same button would flip
|
||||||
const label = `${shortcode} reaction, ${count} ${count === 1 ? 'person' : 'people'}`;
|
// between "🎉 reaction" and "tada reaction" depending on timing (#179).
|
||||||
|
const name = reaction.startsWith('mxc://')
|
||||||
|
? shortcode
|
||||||
|
? `:${shortcode}:`
|
||||||
|
: 'custom emoji'
|
||||||
|
: reaction;
|
||||||
|
const label = `${name} reaction, ${count} ${count === 1 ? 'person' : 'people'}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { Room } from 'matrix-js-sdk';
|
|||||||
import { type Relations } from 'matrix-js-sdk/lib/models/relations';
|
import { type Relations } from 'matrix-js-sdk/lib/models/relations';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { factoryEventSentBy } from '../../../utils/matrix';
|
import { eventWithShortcode, factoryEventSentBy } from '../../../utils/matrix';
|
||||||
import { Reaction, ReactionTooltipMsg } from '../../../components/message';
|
import { Reaction, ReactionTooltipMsg } from '../../../components/message';
|
||||||
import { useRelations } from '../../../hooks/useRelations';
|
import { useRelations } from '../../../hooks/useRelations';
|
||||||
import * as css from './styles.css';
|
import * as css from './styles.css';
|
||||||
@@ -138,6 +138,7 @@ export const Reactions = as<'div', ReactionsProps>(
|
|||||||
key={key}
|
key={key}
|
||||||
mx={mx}
|
mx={mx}
|
||||||
reaction={key}
|
reaction={key}
|
||||||
|
shortcode={rEvents.find(eventWithShortcode)?.getContent().shortcode}
|
||||||
count={events.size}
|
count={events.size}
|
||||||
onClick={canSendReaction ? () => onReactionToggle(mEventId, key) : undefined}
|
onClick={canSendReaction ? () => onReactionToggle(mEventId, key) : undefined}
|
||||||
onContextMenu={handleViewReaction}
|
onContextMenu={handleViewReaction}
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
|
|||||||
key={key}
|
key={key}
|
||||||
mx={mx}
|
mx={mx}
|
||||||
reaction={key}
|
reaction={key}
|
||||||
|
shortcode={Array.from(evts).find(eventWithShortcode)?.getContent().shortcode}
|
||||||
count={evts.size}
|
count={evts.size}
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected={key === selectedKey}
|
aria-selected={key === selectedKey}
|
||||||
|
|||||||
Reference in New Issue
Block a user