Updates
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
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;
|
||||
hoverOpen?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* # 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,
|
||||
hoverOpen,
|
||||
} = 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) {
|
||||
if (isPopover) {
|
||||
modalRoot.style.zIndex = "1000";
|
||||
}
|
||||
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) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setOpen(!open);
|
||||
}}
|
||||
ref={finalTargetRef}
|
||||
onMouseEnter={
|
||||
isPopover && (trigger === "hover" || hoverOpen)
|
||||
? popoverEnterFn
|
||||
: targetWrapperProps?.onMouseEnter
|
||||
}
|
||||
onMouseLeave={
|
||||
isPopover && (trigger === "hover" || hoverOpen)
|
||||
? 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user