A small web app for watching movies and shows easily
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

141 lines
4.0 KiB

import classNames from "classnames";
import FocusTrap from "focus-trap-react";
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useTranslation } from "react-i18next";
import { Transition } from "@/components/utils/Transition";
import {
useInternalOverlayRouter,
useRouterAnchorUpdate,
} from "@/hooks/useOverlayRouter";
import { TurnstileProvider } from "@/stores/turnstile";
export interface OverlayProps {
id: string;
children?: ReactNode;
darken?: boolean;
}
function TurnstileInteractive() {
const { t } = useTranslation();
const [show, setShow] = useState(false);
// this may not rerender with different dom structure, must be exactly the same always
return (
<div
className={classNames(
"absolute w-10/12 max-w-[800px] bg-background-main p-20 rounded-lg select-none z-50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform",
show ? "" : "hidden",
)}
>
<div className="w-full grid lg:grid-cols-[1fr,auto] gap-12 items-center">
<div className="text-left">
<h2 className="text-type-emphasis font-bold text-xl mb-6">
{t("player.turnstile.title")}
</h2>
<p>{t("player.turnstile.description")}</p>
</div>
<TurnstileProvider
isInPopout
onUpdateShow={(shouldShow) => setShow(shouldShow)}
/>
</div>
</div>
);
}
export function OverlayDisplay(props: { children: ReactNode }) {
const router = useInternalOverlayRouter("hello world :)");
const refRouter = useRef(router);
// close router on first mount, we dont want persist routes for overlays
useEffect(() => {
const r = refRouter.current;
r.close();
return () => {
r.close();
};
}, []);
return (
<div className="popout-location">
<TurnstileInteractive />
{props.children}
</div>
);
}
export function OverlayPortal(props: {
children?: ReactNode;
darken?: boolean;
show?: boolean;
close?: () => void;
}) {
const [portalElement, setPortalElement] = useState<Element | null>(null);
const ref = useRef<HTMLDivElement>(null);
const close = props.close;
useEffect(() => {
const element = ref.current?.closest(".popout-location");
setPortalElement(element ?? document.body);
}, []);
return (
<div ref={ref}>
{portalElement
? createPortal(
<Transition show={props.show} animation="none">
<FocusTrap>
<div className="popout-wrapper fixed overflow-hidden pointer-events-auto inset-0 z-[999] select-none">
<Transition animation="fade" isChild>
<div
onClick={close}
className={classNames({
"absolute inset-0": true,
"bg-black opacity-90": props.darken,
})}
/>
</Transition>
<Transition
animation="slide-up"
className="absolute inset-0 pointer-events-none"
isChild
>
{/* a tabable index that does nothing - used so focus trap doesn't error when nothing is rendered yet */}
<div
tabIndex={1}
className="focus:ring-0 focus:outline-none opacity-0"
/>
{props.children}
</Transition>
</div>
</FocusTrap>
</Transition>,
portalElement,
)
: null}
</div>
);
}
export function Overlay(props: OverlayProps) {
const router = useInternalOverlayRouter(props.id);
const realClose = router.close;
// listen for anchor updates
useRouterAnchorUpdate(props.id);
const close = useCallback(() => {
realClose();
}, [realClose]);
return (
<OverlayPortal
close={close}
show={router.isOverlayActive()}
darken={props.darken}
>
{props.children}
</OverlayPortal>
);
}