Skip to content

Commit

Permalink
fix(split-button): add new keyboard functionality to component
Browse files Browse the repository at this point in the history
Updates the keyboard navigation behaviour for `SplitButton`. Up and down
arrow keys can still be used for navigating children buttons but there is no longer
any looping back around. When the children container is open tabbing can now be used
to navigate the children buttons, the tab key takes you down the list and back tab
goes up. When the last child button is focused and tab is pressed it will close the
container and move focus to the next focusable element on the page. When the shift
and tab keys are pressed and the first child button is focused it will close the
container and return focus back to the main button control. Pressing the escape key
when the children container is open will now close the list and return focus back to
the main button control. All other keyboard functionality remains the same

fix #4522
  • Loading branch information
DipperTheDan authored and edleeks87 committed Jul 18, 2022
1 parent 46fd992 commit 3b32940
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 119 deletions.
73 changes: 50 additions & 23 deletions src/components/split-button/split-button.component.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, {
useRef,
useState,
useContext,
useCallback,
useEffect,
useCallback,
useMemo,
useRef,
useState,
} from "react";
import { ThemeContext } from "styled-components";
import PropTypes from "prop-types";
Expand Down Expand Up @@ -50,7 +51,9 @@ const SplitButton = ({
const isToggleButtonFocused = useRef(false);
const isFocusedAfterClosing = useRef(false);
const buttonLabelId = useRef(guid());
const buttonChildren = React.Children.toArray(children);
const buttonChildren = useMemo(() => React.Children.toArray(children), [
children,
]);
const additionalButtons = useRef(buttonChildren.map(() => React.createRef()));
const splitButtonNode = useRef();
const toggleButton = useRef();
Expand All @@ -67,38 +70,61 @@ const SplitButton = ({

const handleKeyDown = useCallback(
(ev) => {
const numOfChildren = children.length - 1;
const currentIndex = additionalButtons.current.findIndex(
(node) => node.current === document.activeElement
);
let nextIndex = -1;

if (Events.isUpKey(ev)) {
nextIndex = currentIndex > 0 ? currentIndex - 1 : numOfChildren;
const refocusMainButton = () => {
setShowAdditionalButtons(false);
toggleButton.current?.focus();
ev.preventDefault();
}
};

if (Events.isDownKey(ev)) {
nextIndex = currentIndex < numOfChildren ? currentIndex + 1 : 0;
ev.preventDefault();
if (Events.isUpKey(ev) && currentIndex > 0) {
nextIndex = currentIndex - 1;
}

if (Events.isTabKey(ev)) {
const elements = Array.from(
document.querySelectorAll(defaultFocusableSelectors)
).filter((el) => Number(el.tabIndex) !== -1);
const indexOf = elements.indexOf(toggleButton.current);
elements[indexOf]?.focus();
if (Events.isDownKey(ev) && currentIndex < buttonChildren.length - 1) {
nextIndex = currentIndex + 1;
}

// timeout enforces that the "hideButtons" method will be run after browser focuses on the next element
setTimeout(hideButtons, 0);
const tabPressed = Events.isTabKey(ev);
const tabShiftPressed = tabPressed && Events.isShiftKey(ev);

if (tabShiftPressed) {
if (currentIndex === 0) {
refocusMainButton();
} else {
nextIndex = currentIndex - 1;
ev.preventDefault();
}
} else if (tabPressed) {
if (currentIndex === buttonChildren.length - 1) {
const elements = Array.from(
document.querySelectorAll(defaultFocusableSelectors)
).filter((el) => Number(el.tabIndex) !== -1);
const indexOf = elements.indexOf(toggleButton.current);

elements[indexOf]?.focus();
isToggleButtonFocused.current = false;
// timeout enforces that the "hideButtons" method will be run after browser focuses on the next element
setTimeout(hideButtons, 0);
} else {
nextIndex = currentIndex + 1;
ev.preventDefault();
}
}

if (nextIndex > -1) {
additionalButtons.current[nextIndex].current.focus();
}

if (Events.isEscKey(ev)) {
refocusMainButton();
}
},
[hideButtons, children]
[hideButtons, buttonChildren]
);
const addListeners = useCallback(() => {
/* istanbul ignore else */
Expand Down Expand Up @@ -233,6 +259,9 @@ const SplitButton = ({
setTimeout(() => {
additionalButtons.current[0]?.current?.focus();
}, 0);
} else if (Events.isEscKey(ev)) {
setShowAdditionalButtons(false);
ev.preventDefault();
}
}

Expand Down Expand Up @@ -262,10 +291,7 @@ const SplitButton = ({
isToggleButtonFocused.current = true;
if (isFocusedAfterClosing.current) {
isFocusedAfterClosing.current = false;
return;
}

showButtons();
}

function renderAdditionalButtons() {
Expand All @@ -280,6 +306,7 @@ const SplitButton = ({
align={align}
minWidth={minWidth}
ref={buttonContainer}
onKeyDown={handleKeyDown}
>
{childrenWithProps()}
</StyledSplitButtonChildrenContainer>
Expand Down
Loading

0 comments on commit 3b32940

Please sign in to comment.