Skip to content

Commit

Permalink
move dropdown focus concerns from hook into CustomDropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
interim17 committed Oct 21, 2024
1 parent 8be9cf4 commit 13d556e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 96 deletions.
86 changes: 73 additions & 13 deletions src/components/CustomDropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, { ReactNode } from "react";
import React, {
KeyboardEventHandler,
ReactNode,
useEffect,
useRef,
useState,
} from "react";
import classNames from "classnames";
import { Dropdown, DropDownProps, MenuProps } from "antd";
import { ButtonClass, DropdownState } from "../../constants/interfaces";
import { DROPDOWN_HOVER_DELAY } from "../../constants";
import NavButton from "../NavButton";
import { useDropdownFocus } from "./useDropdownFocus";

import styles from "./style.css";

Expand All @@ -26,19 +32,73 @@ const CustomDropdown: React.FC<CustomDropdownProps> = ({
disabled,
narrow,
}) => {
const [dropdownState, setDropdownState] = useState<DropdownState>(
DropdownState.CLOSED
);

const triggerRef = useRef<HTMLElement | null>(null);
const dropdownRef = useRef<HTMLDivElement | null>(null);
const closeTimeoutRef = useRef<NodeJS.Timeout | null>(null);

/**
* Prevents the menu wrapper from capturing focus,
* this prevents losing focus to the body
* when "Escape" is pressed.
*/
useEffect(() => {
const element = dropdownRef.current;
if (element) {
const menuElement = element.querySelector(
".ant-dropdown-menu"
) as HTMLElement;
if (menuElement) {
if (dropdownState === DropdownState.FORCED_OPEN) {
menuElement.setAttribute("tabIndex", "-1");
} else if (dropdownState === DropdownState.CLOSED) {
menuElement.setAttribute("tabIndex", "0");
}
}
}
}, [dropdownRef, dropdownState]);

/**
* Calling this hook and exposing the necssary props
* to manage focus in the dropdown
* Manually handling keydown and hover behavior because
* our focus management overrides the defaults of the antd components.
*/
const {
dropdownState,
setDropdownState,
triggerRef,
dropdownRef,
handleKeyDown,
handleMouseLeaveWithDelay,
handleMouseEnter,
} = useDropdownFocus();
const handleKeyDown: KeyboardEventHandler<any> = (event) => {
if (dropdownState === DropdownState.CLOSED) {
if (
(event.key === "Enter" ||
event.key === " " ||
event.key === "ArrowDown") &&
!event.defaultPrevented
) {
event.preventDefault();
setDropdownState(DropdownState.FORCED_OPEN); // Opened by keyboard
}
} else if (event.key === "Escape") {
event.preventDefault();
setDropdownState(DropdownState.CLOSED);
triggerRef.current?.focus();
}
};

const handleMouseEnter = () => {
if (dropdownState === DropdownState.CLOSED) {
setDropdownState(DropdownState.OPEN);
}
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current);
}
};

const handleMouseLeaveWithDelay = () => {
if (dropdownState !== DropdownState.FORCED_OPEN) {
closeTimeoutRef.current = setTimeout(() => {
setDropdownState(DropdownState.CLOSED);
}, DROPDOWN_HOVER_DELAY);
}
};

const menuClassNames = narrow
? classNames(styles.menu, styles.narrow)
Expand Down
83 changes: 0 additions & 83 deletions src/components/CustomDropdown/useDropdownFocus.tsx

This file was deleted.

0 comments on commit 13d556e

Please sign in to comment.