refactor: dropdown button components (#3508)

* refactor: dropdown button components

* chore: dropdowns accessibility improvement

* chore: update module dropdown

* chore: update option content

* chore: hide icon from the peek overview

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
Aaryan Khandelwal 2024-01-31 15:36:55 +05:30 committed by GitHub
parent 3ef0570f6a
commit 0d036e6bf5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 921 additions and 2176 deletions

View file

@ -7,13 +7,16 @@ import { Check, ChevronDown, Search } from "lucide-react";
import { useApplication, useProjectState } from "hooks/store";
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
import { DropdownButton } from "./buttons";
// icons
import { StateGroupIcon, Tooltip } from "@plane/ui";
import { StateGroupIcon } from "@plane/ui";
// helpers
import { cn } from "helpers/common.helper";
// types
import { IState } from "@plane/types";
import { TDropdownProps } from "./types";
// constants
import { BUTTON_VARIANTS_WITH_TEXT } from "./constants";
type Props = TDropdownProps & {
button?: ReactNode;
@ -24,130 +27,6 @@ type Props = TDropdownProps & {
value: string;
};
type ButtonProps = {
className?: string;
dropdownArrow: boolean;
dropdownArrowClassName: string;
hideIcon?: boolean;
hideText?: boolean;
isActive?: boolean;
state: IState | undefined;
tooltip: boolean;
};
const BorderButton = (props: ButtonProps) => {
const {
className,
dropdownArrow,
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
state,
tooltip,
} = props;
return (
<Tooltip tooltipHeading="State" tooltipContent={state?.name ?? "State"} disabled={!tooltip}>
<div
className={cn(
"h-full flex items-center gap-1.5 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 rounded text-xs px-2 py-0.5",
{
"bg-custom-background-80": isActive,
},
className
)}
>
{!hideIcon && (
<StateGroupIcon
stateGroup={state?.group ?? "backlog"}
color={state?.color}
className="h-3 w-3 flex-shrink-0"
/>
)}
{!hideText && <span className="flex-grow truncate">{state?.name ?? "State"}</span>}
{dropdownArrow && (
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
)}
</div>
</Tooltip>
);
};
const BackgroundButton = (props: ButtonProps) => {
const {
className,
dropdownArrow,
dropdownArrowClassName,
hideIcon = false,
hideText = false,
state,
tooltip,
} = props;
return (
<Tooltip tooltipHeading="State" tooltipContent={state?.name ?? "State"} disabled={!tooltip}>
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 bg-custom-background-80",
className
)}
>
{!hideIcon && (
<StateGroupIcon
stateGroup={state?.group ?? "backlog"}
color={state?.color}
className="h-3 w-3 flex-shrink-0"
/>
)}
{!hideText && <span className="flex-grow truncate">{state?.name ?? "State"}</span>}
{dropdownArrow && (
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
)}
</div>
</Tooltip>
);
};
const TransparentButton = (props: ButtonProps) => {
const {
className,
dropdownArrow,
dropdownArrowClassName,
hideIcon = false,
hideText = false,
isActive = false,
state,
tooltip,
} = props;
return (
<Tooltip tooltipHeading="State" tooltipContent={state?.name ?? "State"} disabled={!tooltip}>
<div
className={cn(
"h-full flex items-center gap-1.5 rounded text-xs px-2 py-0.5 hover:bg-custom-background-80",
{
"bg-custom-background-80": isActive,
},
className
)}
>
{!hideIcon && (
<StateGroupIcon
stateGroup={state?.group ?? "backlog"}
color={state?.color}
className="h-3 w-3 flex-shrink-0"
/>
)}
{!hideText && <span className="flex-grow truncate">{state?.name ?? "State"}</span>}
{dropdownArrow && (
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
)}
</div>
</Tooltip>
);
};
export const StateDropdown: React.FC<Props> = observer((props) => {
const {
button,
@ -162,8 +41,8 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
onChange,
placement,
projectId,
showTooltip = false,
tabIndex,
tooltip = false,
value,
} = props;
// states
@ -209,14 +88,35 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
const selectedState = getStateById(value);
const openDropdown = () => {
setIsOpen(true);
const onOpen = () => {
if (!statesList && workspaceSlug) fetchProjectStates(workspaceSlug, projectId);
if (referenceElement) referenceElement.focus();
};
const closeDropdown = () => setIsOpen(false);
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen);
useOutsideClickDetector(dropdownRef, closeDropdown);
const handleClose = () => {
if (isOpen) setIsOpen(false);
if (referenceElement) referenceElement.blur();
};
const toggleDropdown = () => {
if (!isOpen) onOpen();
setIsOpen((prevIsOpen) => !prevIsOpen);
};
const dropdownOnChange = (val: string) => {
onChange(val);
handleClose();
};
const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose);
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
e.preventDefault();
toggleDropdown();
};
useOutsideClickDetector(dropdownRef, handleClose);
return (
<Combobox
@ -225,7 +125,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
tabIndex={tabIndex}
className={cn("h-full", className)}
value={value}
onChange={onChange}
onChange={dropdownOnChange}
disabled={disabled}
onKeyDown={handleKeyDown}
>
@ -235,7 +135,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
ref={setReferenceElement}
type="button"
className={cn("block h-full w-full outline-none", buttonContainerClassName)}
onClick={openDropdown}
onClick={handleOnClick}
>
{button}
</button>
@ -251,70 +151,30 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
},
buttonContainerClassName
)}
onClick={openDropdown}
onClick={handleOnClick}
>
{buttonVariant === "border-with-text" ? (
<BorderButton
state={selectedState}
className={buttonClassName}
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "border-without-text" ? (
<BorderButton
state={selectedState}
className={buttonClassName}
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
) : buttonVariant === "background-with-text" ? (
<BackgroundButton
state={selectedState}
className={buttonClassName}
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
tooltip={tooltip}
/>
) : buttonVariant === "background-without-text" ? (
<BackgroundButton
state={selectedState}
className={buttonClassName}
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
tooltip={tooltip}
hideText
/>
) : buttonVariant === "transparent-with-text" ? (
<TransparentButton
state={selectedState}
className={buttonClassName}
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
/>
) : buttonVariant === "transparent-without-text" ? (
<TransparentButton
state={selectedState}
className={buttonClassName}
dropdownArrow={dropdownArrow && !disabled}
dropdownArrowClassName={dropdownArrowClassName}
hideIcon={hideIcon}
isActive={isOpen}
tooltip={tooltip}
hideText
/>
) : null}
<DropdownButton
className={buttonClassName}
isActive={isOpen}
tooltipHeading="State"
tooltipContent={selectedState?.name ?? "State"}
showTooltip={showTooltip}
variant={buttonVariant}
>
{!hideIcon && (
<StateGroupIcon
stateGroup={selectedState?.group ?? "backlog"}
color={selectedState?.color}
className="h-3 w-3 flex-shrink-0"
/>
)}
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
<span className="flex-grow truncate">{selectedState?.name ?? "State"}</span>
)}
{dropdownArrow && (
<ChevronDown className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)} aria-hidden="true" />
)}
</DropdownButton>
</button>
)}
</Combobox.Button>
@ -348,7 +208,6 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
onClick={closeDropdown}
>
{({ selected }) => (
<>