Skip to content

Commit 2d40278

Browse files
feat: improved accessibility of learning header
1 parent 8ef3a27 commit 2d40278

File tree

3 files changed

+54
-7
lines changed

3 files changed

+54
-7
lines changed

src/learning-header/AuthenticatedUserDropdown.jsx

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import React from 'react';
1+
import React, { useRef } from 'react';
22
import PropTypes from 'prop-types';
3-
43
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
54
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
65
import { getConfig } from '@edx/frontend-platform';
@@ -12,6 +11,30 @@ import LearningUserMenuSlot from '../plugin-slots/LearningUserMenuSlot';
1211
import messages from './messages';
1312

1413
const AuthenticatedUserDropdown = ({ intl, username }) => {
14+
const firstMenuItemRef = useRef(null);
15+
const lastMenuItemRef = useRef(null);
16+
17+
const handleKeyDown = (event) => {
18+
if (event.key === 'Tab') {
19+
event.preventDefault();
20+
21+
const isShiftTab = event.shiftKey;
22+
const currentElement = document.activeElement;
23+
const focusElement = isShiftTab
24+
? currentElement.previousElementSibling
25+
: currentElement.nextElementSibling;
26+
27+
// If the element has reached the start or end of the list, loop the focus
28+
if (isShiftTab && currentElement === firstMenuItemRef.current) {
29+
lastMenuItemRef.current.focus();
30+
} else if (!isShiftTab && currentElement === lastMenuItemRef.current) {
31+
firstMenuItemRef.current.focus();
32+
} else if (focusElement && focusElement.tagName === 'A') {
33+
focusElement.focus();
34+
}
35+
}
36+
};
37+
1538
const dropdownItems = [
1639
{
1740
message: intl.formatMessage(messages.dashboard),
@@ -43,8 +66,13 @@ const AuthenticatedUserDropdown = ({ intl, username }) => {
4366
{username}
4467
</span>
4568
</Dropdown.Toggle>
46-
<Dropdown.Menu className="dropdown-menu-right">
47-
<LearningUserMenuSlot items={dropdownItems} />
69+
<Dropdown.Menu className="dropdown-menu-right" role="menu">
70+
<LearningUserMenuSlot
71+
items={dropdownItems}
72+
firstMenuItemRef={firstMenuItemRef}
73+
lastMenuItemRef={lastMenuItemRef}
74+
handleKeyDown={handleKeyDown}
75+
/>
4876
</Dropdown.Menu>
4977
</Dropdown>
5078
);

src/learning-header/LearningHeaderUserMenuItems.jsx

+13-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,19 @@ import PropTypes from 'prop-types';
33

44
import { Dropdown } from '@openedx/paragon';
55

6-
const LearningHeaderUserMenuItems = ({ items }) => items.map((item) => (
7-
<Dropdown.Item href={item.href}>
6+
const LearningHeaderUserMenuItems = ({
7+
items,
8+
handleKeyDown,
9+
firstMenuItemRef,
10+
lastMenuItemRef,
11+
}) => items.map((item, index) => (
12+
<Dropdown.Item
13+
href={item.href}
14+
role="menuitem"
15+
onKeyDown={handleKeyDown}
16+
// eslint-disable-next-line no-nested-ternary
17+
ref={index === 0 ? firstMenuItemRef : index === items.length - 1 ? lastMenuItemRef : null}
18+
>
819
{item.message}
920
</Dropdown.Item>
1021
));

src/plugin-slots/LearningUserMenuSlot/index.jsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@ import LearningHeaderUserMenuItems, { learningHeaderUserMenuDataShape } from '..
44

55
const LearningUserMenuSlot = ({
66
items,
7+
handleKeyDown,
8+
firstMenuItemRef,
9+
lastMenuItemRef,
710
}) => (
811
<PluginSlot
912
id="learning_user_menu_slot"
1013
slotOptions={{
1114
mergeProps: true,
1215
}}
1316
>
14-
<LearningHeaderUserMenuItems items={items} />
17+
<LearningHeaderUserMenuItems
18+
items={items}
19+
handleKeyDown={handleKeyDown}
20+
firstMenuItemRef={firstMenuItemRef}
21+
lastMenuItemRef={lastMenuItemRef}
22+
/>
1523
</PluginSlot>
1624
);
1725

0 commit comments

Comments
 (0)