mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-26 17:51:57 +01:00
router positions
This commit is contained in:
parent
68441b90e5
commit
4a2a8e89cc
18
src/components/overlays/OverlayRouter.tsx
Normal file
18
src/components/overlays/OverlayRouter.tsx
Normal 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>;
|
||||
}
|
82
src/components/overlays/positions/OverlayAnchorPosition.tsx
Normal file
82
src/components/overlays/positions/OverlayAnchorPosition.tsx
Normal 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>
|
||||
);
|
||||
}
|
20
src/components/overlays/positions/OverlayMobilePosition.tsx
Normal file
20
src/components/overlays/positions/OverlayMobilePosition.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -73,6 +73,7 @@ export function useInternalOverlayRouter(id: string) {
|
||||
export function useOverlayRouter(id: string) {
|
||||
const router = useInternalOverlayRouter(id);
|
||||
return {
|
||||
id,
|
||||
open: router.open,
|
||||
close: router.close,
|
||||
navigate: router.navigate,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { OverlayAnchor } from "@/components/overlays/OverlayAnchor";
|
||||
import { Overlay, OverlayDisplay } from "@/components/overlays/OverlayDisplay";
|
||||
import { OverlayPage } from "@/components/overlays/OverlayPage";
|
||||
import { OverlayRouter } from "@/components/overlays/OverlayRouter";
|
||||
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
|
||||
|
||||
// simple empty view, perfect for putting in tests
|
||||
@ -18,57 +19,59 @@ export default function TestView() {
|
||||
>
|
||||
Open
|
||||
</button>
|
||||
<OverlayAnchor id="test">
|
||||
<div className="h-20 w-20 bg-white" />
|
||||
<OverlayAnchor id={router.id}>
|
||||
<div className="h-20 w-20 mt-64 bg-white" />
|
||||
</OverlayAnchor>
|
||||
<Overlay id="test">
|
||||
<OverlayPage id="test" path="/">
|
||||
<div className="bg-blue-900 p-4">
|
||||
<p>HOME</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/two");
|
||||
}}
|
||||
>
|
||||
open page two
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/one");
|
||||
}}
|
||||
>
|
||||
open page one
|
||||
</button>
|
||||
</div>
|
||||
</OverlayPage>
|
||||
<OverlayPage id="test" path="/one">
|
||||
<div className="bg-blue-900 p-4">
|
||||
<p>ONE</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/");
|
||||
}}
|
||||
>
|
||||
back home
|
||||
</button>
|
||||
</div>
|
||||
</OverlayPage>
|
||||
<OverlayPage id="test" path="/two">
|
||||
<div className="bg-blue-900 p-4">
|
||||
<p>TWO</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/");
|
||||
}}
|
||||
>
|
||||
back home
|
||||
</button>
|
||||
</div>
|
||||
</OverlayPage>
|
||||
<Overlay id={router.id}>
|
||||
<OverlayRouter id={router.id}>
|
||||
<OverlayPage id={router.id} path="/" width={400} height={400}>
|
||||
<div className="bg-blue-900 p-4">
|
||||
<p>HOME</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/two");
|
||||
}}
|
||||
>
|
||||
open page two
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/one");
|
||||
}}
|
||||
>
|
||||
open page one
|
||||
</button>
|
||||
</div>
|
||||
</OverlayPage>
|
||||
<OverlayPage id={router.id} path="/one">
|
||||
<div className="bg-blue-900 p-4">
|
||||
<p>ONE</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/");
|
||||
}}
|
||||
>
|
||||
back home
|
||||
</button>
|
||||
</div>
|
||||
</OverlayPage>
|
||||
<OverlayPage id={router.id} path="/two">
|
||||
<div className="bg-blue-900 p-4">
|
||||
<p>TWO</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
router.navigate("/");
|
||||
}}
|
||||
>
|
||||
back home
|
||||
</button>
|
||||
</div>
|
||||
</OverlayPage>
|
||||
</OverlayRouter>
|
||||
</Overlay>
|
||||
</div>
|
||||
</OverlayDisplay>
|
||||
|
Loading…
Reference in New Issue
Block a user