Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Keyboard navigation spreadsheet layout for issues #3564

Merged
merged 6 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions packages/ui/src/dropdowns/custom-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
const {
buttonClassName = "",
customButtonClassName = "",
customButtonTabIndex = 0,
placement,
children,
className = "",
Expand All @@ -29,6 +30,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
verticalEllipsis = false,
portalElement,
menuButtonOnClick,
onMenuClose,
tabIndex,
closeOnSelect,
} = props;
Expand All @@ -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",
Expand Down Expand Up @@ -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 }) => (
<>
Expand All @@ -103,6 +115,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
if (menuButtonOnClick) menuButtonOnClick();
}}
className={customButtonClassName}
tabIndex={customButtonTabIndex}
>
{customButton}
</button>
Expand All @@ -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>
Expand All @@ -142,6 +156,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
openDropdown();
if (menuButtonOnClick) menuButtonOnClick();
}}
tabIndex={customButtonTabIndex}
>
{label}
{!noChevron && <ChevronDown className="h-3.5 w-3.5" />}
Expand All @@ -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 }) => (
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/dropdowns/helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Placement } from "@blueprintjs/popover2";

export interface IDropdownProps {
customButtonClassName?: string;
customButtonTabIndex?: number;
buttonClassName?: string;
className?: string;
customButton?: JSX.Element;
Expand All @@ -23,6 +24,7 @@ export interface ICustomMenuDropdownProps extends IDropdownProps {
noBorder?: boolean;
verticalEllipsis?: boolean;
menuButtonOnClick?: (...args: any) => void;
onMenuClose?: () => void;
closeOnSelect?: boolean;
portalElement?: Element | null;
}
Expand Down
13 changes: 10 additions & 3 deletions packages/ui/src/hooks/use-dropdown-key-down.tsx
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
2 changes: 1 addition & 1 deletion web/components/common/breadcrumb-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const BreadcrumbLink: React.FC<Props> = (props) => {
const { href, label, icon } = props;
return (
<Tooltip tooltipContent={label} position="bottom">
<li className="flex items-center space-x-2">
<li className="flex items-center space-x-2" tabIndex={-1}>
<div className="flex flex-wrap items-center gap-2.5">
{href ? (
<Link
Expand Down
8 changes: 6 additions & 2 deletions web/components/dropdowns/cycle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Props = TDropdownProps & {
dropdownArrow?: boolean;
dropdownArrowClassName?: string;
onChange: (val: string | null) => void;
onClose?: () => void;
projectId: string;
value: string | null;
};
Expand All @@ -47,6 +48,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName = "",
hideIcon = false,
onChange,
onClose,
placeholder = "Cycle",
placement,
projectId,
Expand Down Expand Up @@ -123,8 +125,10 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
};

const handleClose = () => {
if (isOpen) setIsOpen(false);
if (!isOpen) return;
setIsOpen(false);
if (referenceElement) referenceElement.blur();
onClose && onClose();
};

const toggleDropdown = () => {
Expand Down Expand Up @@ -163,7 +167,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
<button
ref={setReferenceElement}
type="button"
className={cn("block h-full w-full outline-none", buttonContainerClassName)}
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
onClick={handleOnClick}
>
{button}
Expand Down
8 changes: 6 additions & 2 deletions web/components/dropdowns/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Props = TDropdownProps & {
minDate?: Date;
maxDate?: Date;
onChange: (val: Date | null) => void;
onClose?: () => void;
value: Date | string | null;
closeOnSelect?: boolean;
};
Expand All @@ -42,6 +43,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
minDate,
maxDate,
onChange,
onClose,
placeholder = "Date",
placement,
showTooltip = false,
Expand Down Expand Up @@ -74,8 +76,10 @@ export const DateDropdown: React.FC<Props> = (props) => {
};

const handleClose = () => {
if (isOpen) setIsOpen(false);
if (!isOpen) return;
setIsOpen(false);
if (referenceElement) referenceElement.blur();
onClose && onClose();
};

const toggleDropdown = () => {
Expand Down Expand Up @@ -112,7 +116,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
ref={setReferenceElement}
type="button"
className={cn(
"block h-full max-w-full outline-none",
"clickable block h-full max-w-full outline-none",
{
"cursor-not-allowed text-custom-text-200": disabled,
"cursor-pointer": !disabled,
Expand Down
10 changes: 7 additions & 3 deletions web/components/dropdowns/estimate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Props = TDropdownProps & {
dropdownArrow?: boolean;
dropdownArrowClassName?: string;
onChange: (val: number | null) => void;
onClose?: () => void;
projectId: string;
value: number | null;
};
Expand All @@ -46,6 +47,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
dropdownArrowClassName = "",
hideIcon = false,
onChange,
onClose,
placeholder = "Estimate",
placement,
projectId,
Expand Down Expand Up @@ -112,8 +114,10 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
};

const handleClose = () => {
if (isOpen) setIsOpen(false);
if (!isOpen) return;
setIsOpen(false);
if (referenceElement) referenceElement.blur();
onClose && onClose();
};

const toggleDropdown = () => {
Expand Down Expand Up @@ -152,7 +156,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
<button
ref={setReferenceElement}
type="button"
className={cn("block h-full w-full outline-none", buttonContainerClassName)}
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
onClick={handleOnClick}
>
{button}
Expand All @@ -162,7 +166,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
ref={setReferenceElement}
type="button"
className={cn(
"block h-full max-w-full outline-none",
"clickable block h-full max-w-full outline-none",
{
"cursor-not-allowed text-custom-text-200": disabled,
"cursor-pointer": !disabled,
Expand Down
10 changes: 7 additions & 3 deletions web/components/dropdowns/member/project-member.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { BUTTON_VARIANTS_WITH_TEXT } from "../constants";

type Props = {
projectId: string;
onClose?: () => void;
} & MemberDropdownProps;

export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
Expand All @@ -36,6 +37,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
hideIcon = false,
multiple,
onChange,
onClose,
placeholder = "Members",
placement,
projectId,
Expand Down Expand Up @@ -105,8 +107,10 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
};

const handleClose = () => {
if (isOpen) setIsOpen(false);
if (!isOpen) return;
setIsOpen(false);
if (referenceElement) referenceElement.blur();
onClose && onClose();
};

const toggleDropdown = () => {
Expand Down Expand Up @@ -144,7 +148,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
<button
ref={setReferenceElement}
type="button"
className={cn("block h-full w-full outline-none", buttonContainerClassName)}
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
onClick={handleOnClick}
>
{button}
Expand All @@ -154,7 +158,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
ref={setReferenceElement}
type="button"
className={cn(
"block h-full max-w-full outline-none",
"clickable block h-full max-w-full outline-none",
{
"cursor-not-allowed text-custom-text-200": disabled,
"cursor-pointer": !disabled,
Expand Down
1 change: 1 addition & 0 deletions web/components/dropdowns/member/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type MemberDropdownProps = TDropdownProps & {
dropdownArrow?: boolean;
dropdownArrowClassName?: string;
placeholder?: string;
onClose?: () => void;
} & (
| {
multiple: false;
Expand Down
9 changes: 6 additions & 3 deletions web/components/dropdowns/member/workspace-member.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const WorkspaceMemberDropdown: React.FC<MemberDropdownProps> = observer((
hideIcon = false,
multiple,
onChange,
onClose,
placeholder = "Members",
placement,
showTooltip = false,
Expand Down Expand Up @@ -95,8 +96,10 @@ export const WorkspaceMemberDropdown: React.FC<MemberDropdownProps> = observer((
};

const handleClose = () => {
if (isOpen) setIsOpen(false);
if (!isOpen) return;
setIsOpen(false);
if (referenceElement) referenceElement.blur();
onClose && onClose();
};

const toggleDropdown = () => {
Expand Down Expand Up @@ -134,7 +137,7 @@ export const WorkspaceMemberDropdown: React.FC<MemberDropdownProps> = observer((
<button
ref={setReferenceElement}
type="button"
className={cn("block h-full w-full outline-none", buttonContainerClassName)}
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
onClick={handleOnClick}
>
{button}
Expand All @@ -144,7 +147,7 @@ export const WorkspaceMemberDropdown: React.FC<MemberDropdownProps> = observer((
ref={setReferenceElement}
type="button"
className={cn(
"block h-full max-w-full outline-none",
"clickable block h-full max-w-full outline-none",
{
"cursor-not-allowed text-custom-text-200": disabled,
"cursor-pointer": !disabled,
Expand Down
Loading
Loading