Skip to content

Commit

Permalink
Add proper accessibility attributes for at-mentions
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Jan 21, 2025
1 parent f2f720d commit a036793
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 12 deletions.
25 changes: 25 additions & 0 deletions src/sidebar/components/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import {
import type { IconComponent } from '@hypothesis/frontend-shared/lib/types';
import classnames from 'classnames';
import type { Ref, JSX } from 'preact';
import type { TextareaHTMLAttributes } from 'preact/compat';
import {
useCallback,
useEffect,
useId,
useMemo,
useRef,
useState,
Expand Down Expand Up @@ -280,6 +282,27 @@ function TextArea({
[onEditText, textareaRef],
);

const suggestionsId = useId();
const accessibilityAttributes = useMemo((): TextareaHTMLAttributes => {
if (!popoverOpen) {
return {};
}

const selectedSuggestion = userSuggestions[highlightedSuggestion];
const activeDescendant = selectedSuggestion
? `${suggestionsId}-${selectedSuggestion.username}`
: undefined;

return {
role: 'combobox',
'aria-controls': suggestionsId,
'aria-expanded': true,
'aria-autocomplete': 'list',
'aria-haspopup': 'listbox',
'aria-activedescendant': activeDescendant,
};
}, [highlightedSuggestion, suggestionsId, popoverOpen, userSuggestions]);

return (
<div className="relative">
<textarea
Expand Down Expand Up @@ -332,6 +355,7 @@ function TextArea({
checkForMentionAtCaret(e.target as HTMLTextAreaElement);
}}
ref={textareaRef}
{...accessibilityAttributes}
/>
{mentionsEnabled && (
<MentionPopover
Expand All @@ -341,6 +365,7 @@ function TextArea({
users={userSuggestions}
highlightedSuggestion={highlightedSuggestion}
onSelectUser={insertMention}
suggestionsId={suggestionsId}
/>
)}
</div>
Expand Down
5 changes: 5 additions & 0 deletions src/sidebar/components/MentionPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type MentionPopoverProps = Pick<
highlightedSuggestion: number;
/** Invoked when a user is selected */
onSelectUser: (selectedSuggestion: UserItem) => void;
/** ID attribute to set to the user suggestions listbox */
suggestionsId: string;
};

/**
Expand All @@ -26,6 +28,7 @@ export default function MentionPopover({
users,
onSelectUser,
highlightedSuggestion,
suggestionsId,
...popoverProps
}: MentionPopoverProps) {
return (
Expand All @@ -34,6 +37,7 @@ export default function MentionPopover({
className="flex-col gap-y-0.5"
role="listbox"
aria-orientation="vertical"
id={suggestionsId}
>
{users.map((u, index) => (
// These options are indirectly handled via keyboard event
Expand All @@ -42,6 +46,7 @@ export default function MentionPopover({
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<li
key={u.username}
id={`${suggestionsId}-${u.username}`}
className={classnames(
'flex justify-between items-center',
'rounded p-2 hover:bg-grey-2',
Expand Down
47 changes: 35 additions & 12 deletions src/sidebar/components/test/MarkdownEditor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ describe('MarkdownEditor', () => {
);
}

function typeInTextarea(wrapper, text, key = undefined) {
const textarea = wrapper.find('textarea');
const textareaDOMNode = textarea.getDOMNode();

textareaDOMNode.value = text;
textareaDOMNode.selectionStart = text.length;

textarea.simulate('keyup', { key });
}

const commands = [
{
command: 'Bold',
Expand Down Expand Up @@ -380,18 +390,6 @@ describe('MarkdownEditor', () => {
});

context('when @mentions are enabled', () => {
function typeInTextarea(wrapper, text, key = undefined) {
const textarea = wrapper.find('textarea');
const textareaDOMNode = textarea.getDOMNode();

textareaDOMNode.value = text;
textareaDOMNode.selectionStart = text.length;
act(() =>
textareaDOMNode.dispatchEvent(new KeyboardEvent('keyup', { key })),
);
wrapper.update();
}

function keyDownInTextarea(wrapper, key) {
const textarea = wrapper.find('textarea');
textarea.simulate('keydown', { key });
Expand Down Expand Up @@ -638,6 +636,31 @@ describe('MarkdownEditor', () => {
.filterWhere(el => el.text() === 'Preview');
previewButton.simulate('click');

return wrapper;
},
},
{
name: 'Suggestions popover',
content: () => {
$imports.$restore({
// We need to render MentionPopover and its children, as some aria
// attributes reference elements rendered by it
'./MentionPopover': true,
});

const wrapper = createComponent(
{
mentionsEnabled: true,
usersForMentions: [
{ username: 'one', displayName: 'johndoe' },
{ username: 'two', displayName: 'johndoe' },
{ username: 'three', displayName: 'johndoe' },
],
},
{ connected: true },
);
typeInTextarea(wrapper, '@johndoe');

return wrapper;
},
},
Expand Down

0 comments on commit a036793

Please sign in to comment.