UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: дизайн и русификация God Mode
This commit is contained in:
+121
-83
@@ -4,11 +4,10 @@
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import type { Placement } from "@popperjs/core";
|
||||
import { usePopper } from "react-popper";
|
||||
// headless ui
|
||||
import { Popover, Portal, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { Button } from "@plane/propel/button";
|
||||
|
||||
@@ -45,93 +44,132 @@ export function FiltersDropdown(props: Props) {
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | HTMLDivElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: placement ?? "auto",
|
||||
});
|
||||
|
||||
return (
|
||||
<Popover as="div">
|
||||
{({ open, close }) => (
|
||||
<>
|
||||
<Popover.Button as={React.Fragment}>
|
||||
{menuButton ? (
|
||||
<button
|
||||
type="button"
|
||||
ref={setReferenceElement}
|
||||
className={menuButtonWrapperClassName}
|
||||
disabled={disabled}
|
||||
>
|
||||
{typeof menuButton === "function" ? menuButton({ open }) : menuButton}
|
||||
</button>
|
||||
) : (
|
||||
<div ref={setReferenceElement}>
|
||||
<div className="hidden @4xl:flex">
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant="secondary"
|
||||
prependIcon={icon}
|
||||
tabIndex={tabIndex}
|
||||
className="nodedc-toolbar-pill nodedc-toolbar-pill-wide relative"
|
||||
data-active={open}
|
||||
size="lg"
|
||||
>
|
||||
<>
|
||||
<div className={`${open ? "text-[rgb(var(--nodedc-accent-rgb))]" : "text-secondary"}`}>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
{isFiltersApplied && (
|
||||
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-accent-primary" />
|
||||
)}
|
||||
</>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex @4xl:hidden">
|
||||
<Button
|
||||
disabled={disabled}
|
||||
ref={setReferenceElement}
|
||||
variant="secondary"
|
||||
tabIndex={tabIndex}
|
||||
className="nodedc-toolbar-pill nodedc-toolbar-pill-wide"
|
||||
size="lg"
|
||||
>
|
||||
{miniIcon || title}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
const toggleDropdown = useCallback(() => {
|
||||
if (!disabled) setIsOpen((current) => !current);
|
||||
}, [disabled]);
|
||||
|
||||
const closeDropdown = useCallback(() => setIsOpen(false), []);
|
||||
|
||||
const handleTriggerKeyDown = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (disabled) return;
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
toggleDropdown();
|
||||
}
|
||||
if (event.key === "Escape") closeDropdown();
|
||||
},
|
||||
[closeDropdown, disabled, toggleDropdown]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const handleDocumentMouseDown = (event: MouseEvent) => {
|
||||
const target = event.target;
|
||||
if (!(target instanceof Node)) return;
|
||||
|
||||
if (referenceElement?.contains(target) || popperElement?.contains(target)) return;
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleDocumentKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") closeDropdown();
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleDocumentMouseDown);
|
||||
document.addEventListener("keydown", handleDocumentKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleDocumentMouseDown);
|
||||
document.removeEventListener("keydown", handleDocumentKeyDown);
|
||||
};
|
||||
}, [closeDropdown, isOpen, popperElement, referenceElement]);
|
||||
|
||||
const dropdownPanel =
|
||||
isOpen &&
|
||||
createPortal(
|
||||
<div className="fixed z-[760] translate-y-0">
|
||||
<div
|
||||
className={`nodedc-dropdown-surface my-1 overflow-hidden ${dropdownClassName ?? ""}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div
|
||||
className={`flex max-h-[30rem] w-[18.75rem] flex-col overflow-hidden lg:max-h-[37.5rem] ${
|
||||
dropdownContentClassName ?? ""
|
||||
}`}
|
||||
>
|
||||
<Portal>
|
||||
{/** translate-y-0 is a hack to create new stacking context. Required for safari */}
|
||||
<Popover.Panel className="fixed z-[760] translate-y-0">
|
||||
<div
|
||||
className={`nodedc-dropdown-surface my-1 overflow-hidden ${dropdownClassName ?? ""}`}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div
|
||||
className={`flex max-h-[30rem] w-[18.75rem] flex-col overflow-hidden lg:max-h-[37.5rem] ${
|
||||
dropdownContentClassName ?? ""
|
||||
}`}
|
||||
>
|
||||
{typeof children === "function" ? children({ closeDropdown: close }) : children}
|
||||
</div>
|
||||
{typeof children === "function" ? children({ closeDropdown }) : children}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{menuButton ? (
|
||||
<button
|
||||
type="button"
|
||||
ref={setReferenceElement}
|
||||
className={menuButtonWrapperClassName}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{typeof menuButton === "function" ? menuButton({ open: isOpen }) : menuButton}
|
||||
</button>
|
||||
) : (
|
||||
<div
|
||||
ref={setReferenceElement}
|
||||
role="button"
|
||||
tabIndex={disabled ? -1 : tabIndex}
|
||||
aria-disabled={disabled}
|
||||
onClick={toggleDropdown}
|
||||
onKeyDown={handleTriggerKeyDown}
|
||||
>
|
||||
<div className="hidden @4xl:flex">
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant="secondary"
|
||||
prependIcon={icon}
|
||||
tabIndex={-1}
|
||||
className="nodedc-toolbar-pill nodedc-toolbar-pill-wide relative"
|
||||
data-active={isOpen}
|
||||
size="lg"
|
||||
>
|
||||
<>
|
||||
<div className={`${isOpen ? "text-[rgb(var(--nodedc-accent-rgb))]" : "text-secondary"}`}>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Portal>
|
||||
</Transition>
|
||||
</>
|
||||
{isFiltersApplied && (
|
||||
<span className="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-accent-primary" />
|
||||
)}
|
||||
</>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex @4xl:hidden">
|
||||
<Button
|
||||
disabled={disabled}
|
||||
variant="secondary"
|
||||
tabIndex={-1}
|
||||
className="nodedc-toolbar-pill nodedc-toolbar-pill-wide"
|
||||
size="lg"
|
||||
>
|
||||
{miniIcon || title}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
{dropdownPanel}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user