5 changed files with 174 additions and 50 deletions
@ -0,0 +1,18 @@ |
|||||||
|
import { ReactNode } from "react"; |
||||||
|
|
||||||
|
import { OverlayAnchorPosition } from "@/components/overlays/positions/OverlayAnchorPosition"; |
||||||
|
import { OverlayMobilePosition } from "@/components/overlays/positions/OverlayMobilePosition"; |
||||||
|
import { useIsMobile } from "@/hooks/useIsMobile"; |
||||||
|
|
||||||
|
interface OverlayRouterProps { |
||||||
|
children?: ReactNode; |
||||||
|
id: string; |
||||||
|
} |
||||||
|
|
||||||
|
export function OverlayRouter(props: OverlayRouterProps) { |
||||||
|
const { isMobile } = useIsMobile(); |
||||||
|
const content = props.children; |
||||||
|
|
||||||
|
if (isMobile) return <OverlayMobilePosition>{content}</OverlayMobilePosition>; |
||||||
|
return <OverlayAnchorPosition id={props.id}>{content}</OverlayAnchorPosition>; |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
import classNames from "classnames"; |
||||||
|
import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; |
||||||
|
|
||||||
|
import { createOverlayAnchorEvent } from "@/components/overlays/OverlayAnchor"; |
||||||
|
|
||||||
|
interface AnchorPositionProps { |
||||||
|
children?: ReactNode; |
||||||
|
id: string; |
||||||
|
className?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export function OverlayAnchorPosition(props: AnchorPositionProps) { |
||||||
|
const ref = useRef<HTMLDivElement>(null); |
||||||
|
const [left, setLeft] = useState<number>(0); |
||||||
|
const [top, setTop] = useState<number>(0); |
||||||
|
const [cardRect, setCardRect] = useState<DOMRect | null>(null); |
||||||
|
const [anchorRect, setAnchorRect] = useState<DOMRect | null>(null); |
||||||
|
|
||||||
|
const calculateAndSetCoords = useCallback( |
||||||
|
(anchor: DOMRect, card: DOMRect) => { |
||||||
|
const buttonCenter = anchor.left + anchor.width / 2; |
||||||
|
const bottomReal = window.innerHeight - anchor.bottom; |
||||||
|
|
||||||
|
setTop( |
||||||
|
window.innerHeight - bottomReal - anchor.height - card.height - 30 |
||||||
|
); |
||||||
|
setLeft( |
||||||
|
Math.min( |
||||||
|
buttonCenter - card.width / 2, |
||||||
|
window.innerWidth - card.width - 30 |
||||||
|
) |
||||||
|
); |
||||||
|
}, |
||||||
|
[] |
||||||
|
); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!anchorRect || !cardRect) return; |
||||||
|
calculateAndSetCoords(anchorRect, cardRect); |
||||||
|
}, [anchorRect, calculateAndSetCoords, cardRect]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (!ref.current) return; |
||||||
|
function checkBox() { |
||||||
|
const divRect = ref.current?.getBoundingClientRect(); |
||||||
|
setCardRect(divRect ?? null); |
||||||
|
} |
||||||
|
checkBox(); |
||||||
|
const observer = new ResizeObserver(checkBox); |
||||||
|
observer.observe(ref.current); |
||||||
|
return () => { |
||||||
|
observer.disconnect(); |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const evtStr = createOverlayAnchorEvent(props.id); |
||||||
|
if ((window as any)[evtStr]) setAnchorRect((window as any)[evtStr]); |
||||||
|
function listen(ev: CustomEvent<DOMRect>) { |
||||||
|
setAnchorRect(ev.detail); |
||||||
|
} |
||||||
|
document.addEventListener(evtStr, listen as any); |
||||||
|
return () => { |
||||||
|
document.removeEventListener(evtStr, listen as any); |
||||||
|
}; |
||||||
|
}, [props.id]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
ref={ref} |
||||||
|
style={{ |
||||||
|
transform: `translateX(${left}px) translateY(${top}px)`, |
||||||
|
}} |
||||||
|
className={classNames([ |
||||||
|
"pointer-events-auto z-10 inline-block origin-top-left touch-none overflow-hidden", |
||||||
|
props.className, |
||||||
|
])} |
||||||
|
> |
||||||
|
{props.children} |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
import classNames from "classnames"; |
||||||
|
import { ReactNode } from "react"; |
||||||
|
|
||||||
|
interface MobilePositionProps { |
||||||
|
children?: ReactNode; |
||||||
|
className?: string; |
||||||
|
} |
||||||
|
|
||||||
|
export function OverlayMobilePosition(props: MobilePositionProps) { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className={classNames([ |
||||||
|
"pointer-events-auto z-10 inline-block origin-top-left touch-none overflow-hidden", |
||||||
|
props.className, |
||||||
|
])} |
||||||
|
> |
||||||
|
{props.children} |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue