/** * Copyright (c) 2023-present Plane Software, Inc. and contributors * SPDX-License-Identifier: AGPL-3.0-only * See the LICENSE file for details. */ import { Combobox } from "@headlessui/react"; import React, { createContext, useCallback, useContext, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { usePopper } from "react-popper"; import { useOutsideClickDetector } from "@plane/hooks"; import { CheckIcon, ChevronDownIcon } from "@plane/propel/icons"; // plane helpers // hooks import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down"; // helpers import { cn } from "../utils"; // types import type { ICustomSelectItemProps, ICustomSelectProps } from "./helper"; // Context to share the close handler with option components const DropdownContext = createContext<() => void>(() => {}); function CustomSelect(props: ICustomSelectProps) { const { customButtonClassName = "", buttonClassName = "", placement, children, className = "", customButton, disabled = false, input = false, label, maxHeight = "md", noChevron = false, onChange, optionsClassName = "", value, tabIndex, } = props; // states const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const [isOpen, setIsOpen] = useState(false); // refs const dropdownRef = useRef(null); const { styles, attributes } = usePopper(referenceElement, popperElement, { placement: placement ?? "bottom-start", }); const openDropdown = useCallback(() => { setIsOpen(true); if (referenceElement) referenceElement.focus(); }, [referenceElement]); const closeDropdown = useCallback(() => setIsOpen(false), []); const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen); useOutsideClickDetector(dropdownRef, closeDropdown); const toggleDropdown = useCallback(() => { if (isOpen) closeDropdown(); else openDropdown(); }, [closeDropdown, isOpen, openDropdown]); return ( { onChange?.(val); closeDropdown(); }} className={cn("relative flex-shrink-0 text-left", className)} onKeyDown={handleKeyDown} disabled={disabled} > <> {customButton ? ( ) : ( )} {isOpen && createPortal(
{children}
, document.body )}
); } function Option(props: ICustomSelectItemProps) { const { children, value, className } = props; const closeDropdown = useContext(DropdownContext); const handleClick = useCallback(() => { // Close dropdown for both new and already-selected options. // Use setTimeout to ensure HeadlessUI's onChange handler fires first for new selections. // For already-selected options, this ensures the dropdown closes since onChange won't fire. setTimeout(() => { closeDropdown(); }, 0); }, [closeDropdown]); return ( cn( "flex cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none", { "bg-layer-transparent-hover": active, }, className ) } onClick={handleClick} > {({ selected }) => (
{children} {selected && }
)}
); } CustomSelect.Option = Option; export { CustomSelect };