182 lines
5.5 KiB
TypeScript
182 lines
5.5 KiB
TypeScript
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
|
|
import ModalComponent from "../(partials)/ModalComponent";
|
|
import PopoverComponent from "../(partials)/PopoverComponent";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
export const TWUIPopoverStyles = [
|
|
"top",
|
|
"bottom",
|
|
"left",
|
|
"right",
|
|
"transform",
|
|
"bottom-left",
|
|
"bottom-right",
|
|
] as const;
|
|
export const TWUIPopoverTriggers = ["hover", "click"] as const;
|
|
|
|
export type TWUI_MODAL_PROPS = DetailedHTMLProps<
|
|
HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
> & {
|
|
target?: React.ReactNode;
|
|
targetRef?: React.RefObject<HTMLDivElement>;
|
|
popoverReferenceRef?: React.RefObject<HTMLElement | null>;
|
|
targetWrapperProps?: React.DetailedHTMLProps<
|
|
React.HTMLAttributes<HTMLDivElement>,
|
|
HTMLDivElement
|
|
>;
|
|
setOpen?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
open?: boolean;
|
|
isPopover?: boolean;
|
|
position?: (typeof TWUIPopoverStyles)[number];
|
|
trigger?: (typeof TWUIPopoverTriggers)[number];
|
|
debounce?: number;
|
|
onClose?: () => any;
|
|
};
|
|
|
|
/**
|
|
* # Modal Component
|
|
* @ID twui-modal-root
|
|
* @className twui-modal-content
|
|
* @className twui-modal
|
|
* @ID twui-popover-root
|
|
* @className twui-popover-content
|
|
* @className twui-popover-target
|
|
*/
|
|
export default function Modal(props: TWUI_MODAL_PROPS) {
|
|
const {
|
|
target,
|
|
targetRef,
|
|
targetWrapperProps,
|
|
open: existingOpen,
|
|
setOpen: existingSetOpen,
|
|
isPopover,
|
|
popoverReferenceRef,
|
|
trigger = "hover",
|
|
debounce = 500,
|
|
onClose,
|
|
} = props;
|
|
|
|
const [ready, setReady] = React.useState(false);
|
|
const [open, setOpen] = React.useState(existingOpen || false);
|
|
|
|
React.useEffect(() => {
|
|
const IDName = isPopover ? "twui-popover-root" : "twui-modal-root";
|
|
const modalRoot = document.getElementById(IDName);
|
|
|
|
if (modalRoot) {
|
|
setReady(true);
|
|
} else {
|
|
const newModalRootEl = document.createElement("div");
|
|
newModalRootEl.id = IDName;
|
|
document.body.appendChild(newModalRootEl);
|
|
setReady(true);
|
|
}
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
existingSetOpen?.(open);
|
|
if (open == false) onClose?.();
|
|
}, [open]);
|
|
|
|
React.useEffect(() => {
|
|
setOpen(existingOpen || false);
|
|
}, [existingOpen]);
|
|
|
|
const finalTargetRef = targetRef || React.useRef<HTMLDivElement>(null);
|
|
const finalPopoverReferenceRef = popoverReferenceRef || finalTargetRef;
|
|
|
|
const popoverTargetActiveRef = React.useRef(false);
|
|
const popoverContentActiveRef = React.useRef(false);
|
|
|
|
let closeTimeout: any;
|
|
|
|
const popoverEnterFn = React.useCallback((e: any) => {
|
|
popoverTargetActiveRef.current = true;
|
|
popoverContentActiveRef.current = false;
|
|
setOpen(true);
|
|
props.onMouseEnter?.(e);
|
|
}, []);
|
|
|
|
const popoverLeaveFn = React.useCallback((e: any) => {
|
|
window.clearTimeout(closeTimeout);
|
|
closeTimeout = setTimeout(() => {
|
|
// if (popoverTargetActiveRef.current) {
|
|
// popoverTargetActiveRef.current = false;
|
|
// return;
|
|
// }
|
|
|
|
if (popoverContentActiveRef.current) {
|
|
popoverContentActiveRef.current = false;
|
|
return;
|
|
}
|
|
setOpen(false);
|
|
}, debounce);
|
|
props.onMouseLeave?.(e);
|
|
}, []);
|
|
|
|
const handleClickOutside = React.useCallback((e: MouseEvent) => {
|
|
const targetEl = e.target as HTMLElement;
|
|
|
|
const closestWrapper = targetEl.closest(".twui-popover-content");
|
|
const closestTarget = targetEl.closest(".twui-popover-target");
|
|
|
|
if (closestTarget) return;
|
|
|
|
if (!closestWrapper) {
|
|
return setOpen(false);
|
|
}
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
if (!isPopover) return;
|
|
document.addEventListener("click", handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener("click", handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<React.Fragment>
|
|
{target ? (
|
|
<div
|
|
{...targetWrapperProps}
|
|
onClick={(e) => setOpen(!open)}
|
|
ref={finalTargetRef}
|
|
onMouseEnter={
|
|
isPopover && trigger === "hover"
|
|
? popoverEnterFn
|
|
: targetWrapperProps?.onMouseEnter
|
|
}
|
|
onMouseLeave={
|
|
isPopover && trigger === "hover"
|
|
? popoverLeaveFn
|
|
: targetWrapperProps?.onMouseLeave
|
|
}
|
|
className={twMerge(
|
|
"twui-popover-target",
|
|
targetWrapperProps?.className
|
|
)}
|
|
>
|
|
{target}
|
|
</div>
|
|
) : null}
|
|
{ready ? (
|
|
isPopover ? (
|
|
<PopoverComponent
|
|
{...props}
|
|
open={open}
|
|
setOpen={setOpen}
|
|
targetElRef={finalPopoverReferenceRef}
|
|
debounce={debounce}
|
|
popoverTargetActiveRef={popoverTargetActiveRef}
|
|
popoverContentActiveRef={popoverContentActiveRef}
|
|
/>
|
|
) : (
|
|
<ModalComponent {...props} open={open} setOpen={setOpen} />
|
|
)
|
|
) : null}
|
|
</React.Fragment>
|
|
);
|
|
}
|