router positions

This commit is contained in:
mrjvs 2023-10-09 21:25:52 +02:00
parent 68441b90e5
commit 4a2a8e89cc
5 changed files with 174 additions and 50 deletions

View File

@ -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>;
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -73,6 +73,7 @@ export function useInternalOverlayRouter(id: string) {
export function useOverlayRouter(id: string) { export function useOverlayRouter(id: string) {
const router = useInternalOverlayRouter(id); const router = useInternalOverlayRouter(id);
return { return {
id,
open: router.open, open: router.open,
close: router.close, close: router.close,
navigate: router.navigate, navigate: router.navigate,

View File

@ -1,6 +1,7 @@
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor"; import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay"; import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay";
import { OverlayPage } from "@/components/overlays/OverlayPage"; import { OverlayPage } from "@/components/overlays/OverlayPage";
import { OverlayRouter } from "@/components/overlays/OverlayRouter";
import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { useOverlayRouter } from "@/hooks/useOverlayRouter";
// simple empty view, perfect for putting in tests // simple empty view, perfect for putting in tests
@ -18,57 +19,59 @@ export default function TestView() {
> >
Open Open
</button> </button>
<OverlayAnchor id="test"> <OverlayAnchor id={router.id}>
<div className="h-20 w-20 bg-white" /> <div className="h-20 w-20 mt-64 bg-white" />
</OverlayAnchor> </OverlayAnchor>
<Overlay id="test"> <Overlay id={router.id}>
<OverlayPage id="test" path="/"> <OverlayRouter id={router.id}>
<div className="bg-blue-900 p-4"> <OverlayPage id={router.id} path="/" width={400} height={400}>
<p>HOME</p> <div className="bg-blue-900 p-4">
<button <p>HOME</p>
type="button" <button
onClick={() => { type="button"
router.navigate("/two"); onClick={() => {
}} router.navigate("/two");
> }}
open page two >
</button> open page two
<button </button>
type="button" <button
onClick={() => { type="button"
router.navigate("/one"); onClick={() => {
}} router.navigate("/one");
> }}
open page one >
</button> open page one
</div> </button>
</OverlayPage> </div>
<OverlayPage id="test" path="/one"> </OverlayPage>
<div className="bg-blue-900 p-4"> <OverlayPage id={router.id} path="/one">
<p>ONE</p> <div className="bg-blue-900 p-4">
<button <p>ONE</p>
type="button" <button
onClick={() => { type="button"
router.navigate("/"); onClick={() => {
}} router.navigate("/");
> }}
back home >
</button> back home
</div> </button>
</OverlayPage> </div>
<OverlayPage id="test" path="/two"> </OverlayPage>
<div className="bg-blue-900 p-4"> <OverlayPage id={router.id} path="/two">
<p>TWO</p> <div className="bg-blue-900 p-4">
<button <p>TWO</p>
type="button" <button
onClick={() => { type="button"
router.navigate("/"); onClick={() => {
}} router.navigate("/");
> }}
back home >
</button> back home
</div> </button>
</OverlayPage> </div>
</OverlayPage>
</OverlayRouter>
</Overlay> </Overlay>
</div> </div>
</OverlayDisplay> </OverlayDisplay>