feat: Keyboard navigation spreadsheet layout for issues (#3564)

* enable keyboard navigation for spreadsheet layout

* move the logic to table level instead of cell level

* fix perf issue that made it unusable

* fix scroll issue with navigation

* fix build errors
This commit is contained in:
rahulramesha 2024-02-08 11:49:00 +05:30 committed by GitHub
parent a43dfc097d
commit fb3dd77b66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 368 additions and 126 deletions

View file

@ -15,6 +15,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
const {
buttonClassName = "",
customButtonClassName = "",
customButtonTabIndex = 0,
placement,
children,
className = "",
@ -29,6 +30,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
verticalEllipsis = false,
portalElement,
menuButtonOnClick,
onMenuClose,
tabIndex,
closeOnSelect,
} = props;
@ -47,18 +49,27 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
setIsOpen(true);
if (referenceElement) referenceElement.focus();
};
const closeDropdown = () => setIsOpen(false);
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen);
const closeDropdown = () => {
isOpen && onMenuClose && onMenuClose();
setIsOpen(false);
};
const handleOnChange = () => {
if (closeOnSelect) closeDropdown();
};
const selectActiveItem = () => {
const activeItem: HTMLElement | undefined | null = dropdownRef.current?.querySelector(
`[data-headlessui-state="active"] button`
);
activeItem?.click();
};
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen, selectActiveItem);
useOutsideClickDetector(dropdownRef, closeDropdown);
let menuItems = (
<Menu.Items
className="fixed z-10"
onClick={() => {
if (closeOnSelect) closeDropdown();
}}
static
>
<Menu.Items className="fixed z-10" static>
<div
className={cn(
"my-1 overflow-y-scroll rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg focus:outline-none min-w-[12rem] whitespace-nowrap",
@ -89,7 +100,8 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
ref={dropdownRef}
tabIndex={tabIndex}
className={cn("relative w-min text-left", className)}
onKeyDown={handleKeyDown}
onKeyDownCapture={handleKeyDown}
onChange={handleOnChange}
>
{({ open }) => (
<>
@ -103,6 +115,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
if (menuButtonOnClick) menuButtonOnClick();
}}
className={customButtonClassName}
tabIndex={customButtonTabIndex}
>
{customButton}
</button>
@ -122,6 +135,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
className={`relative grid place-items-center rounded p-1 text-custom-text-200 outline-none hover:text-custom-text-100 ${
disabled ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
tabIndex={customButtonTabIndex}
>
<MoreHorizontal className={`h-3.5 w-3.5 ${verticalEllipsis ? "rotate-90" : ""}`} />
</button>
@ -142,6 +156,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
openDropdown();
if (menuButtonOnClick) menuButtonOnClick();
}}
tabIndex={customButtonTabIndex}
>
{label}
{!noChevron && <ChevronDown className="h-3.5 w-3.5" />}
@ -159,6 +174,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
const MenuItem: React.FC<ICustomMenuItemProps> = (props) => {
const { children, onClick, className = "" } = props;
return (
<Menu.Item as="div">
{({ active, close }) => (

View file

@ -3,6 +3,7 @@ import { Placement } from "@blueprintjs/popover2";
export interface IDropdownProps {
customButtonClassName?: string;
customButtonTabIndex?: number;
buttonClassName?: string;
className?: string;
customButton?: JSX.Element;
@ -23,6 +24,7 @@ export interface ICustomMenuDropdownProps extends IDropdownProps {
noBorder?: boolean;
verticalEllipsis?: boolean;
menuButtonOnClick?: (...args: any) => void;
onMenuClose?: () => void;
closeOnSelect?: boolean;
portalElement?: Element | null;
}

View file

@ -1,16 +1,23 @@
import { useCallback } from "react";
type TUseDropdownKeyDown = {
(onOpen: () => void, onClose: () => void, isOpen: boolean): (event: React.KeyboardEvent<HTMLElement>) => void;
(
onOpen: () => void,
onClose: () => void,
isOpen: boolean,
selectActiveItem?: () => void
): (event: React.KeyboardEvent<HTMLElement>) => void;
};
export const useDropdownKeyDown: TUseDropdownKeyDown = (onOpen, onClose, isOpen) => {
export const useDropdownKeyDown: TUseDropdownKeyDown = (onOpen, onClose, isOpen, selectActiveItem?) => {
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLElement>) => {
if (event.key === "Enter") {
event.stopPropagation();
if (!isOpen) {
event.stopPropagation();
onOpen();
} else {
selectActiveItem && selectActiveItem();
}
} else if (event.key === "Escape" && isOpen) {
event.stopPropagation();