diff --git a/src/components/overlays/OverlayDisplay.tsx b/src/components/overlays/OverlayDisplay.tsx index 9cbc03dc..99613a82 100644 --- a/src/components/overlays/OverlayDisplay.tsx +++ b/src/components/overlays/OverlayDisplay.tsx @@ -3,7 +3,7 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { Transition } from "@/components/Transition"; -import { useOverlayRouter } from "@/hooks/useOverlayRouter"; +import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter"; export interface OverlayProps { id: string; @@ -16,11 +16,21 @@ export function OverlayDisplay(props: { children: ReactNode }) { } export function Overlay(props: OverlayProps) { - const router = useOverlayRouter(props.id); + const router = useInternalOverlayRouter(props.id); + const refRouter = useRef(router); const [portalElement, setPortalElement] = useState(null); const ref = useRef(null); const target = useRef(null); + // close router on first mount, we dont want persist routes for overlays + useEffect(() => { + const r = refRouter.current; + r.close(); + return () => { + r.close(); + }; + }, []); + useEffect(() => { function listen(e: MouseEvent) { target.current = e.target as Element; diff --git a/src/components/overlays/OverlayPage.tsx b/src/components/overlays/OverlayPage.tsx index 17a71dbf..9d31360f 100644 --- a/src/components/overlays/OverlayPage.tsx +++ b/src/components/overlays/OverlayPage.tsx @@ -3,32 +3,37 @@ import { ReactNode } from "react"; import { Transition } from "@/components/Transition"; import { useIsMobile } from "@/hooks/useIsMobile"; +import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter"; interface Props { + id: string; + path: string; children?: ReactNode; - show?: boolean; className?: string; height?: number; width?: number; - active?: boolean; // true if a child view is loaded } export function OverlayPage(props: Props) { + const router = useInternalOverlayRouter(props.id); + const backwards = router.showBackwardsTransition(props.path); + const show = router.isCurrentPage(props.path); + const { isMobile } = useIsMobile(); const width = !isMobile ? `${props.width}px` : "100%"; return (
v.length > 0); +} + +function joinPath(path: string[]): string { + return `/${path.join("/")}`; +} + +export function useInternalOverlayRouter(id: string) { const [route, setRoute] = useQueryParam("r"); - const routeParts = (route ?? "").split("/").filter((v) => v.length > 0); - const routerActive = routeParts.length > 0 && routeParts[0] === id; + const transition = useOverlayStore((s) => s.transition); + const setTransition = useOverlayStore((s) => s.setTransition); + const routerActive = !!route && route.startsWith(`/${id}`); function navigate(path: string) { - const newRoute = [id, ...path.split("/").filter((v) => v.length > 0)]; - setRoute(newRoute.join("/")); + const oldRoute = route; + const newRoute = joinPath(splitPath(path, id)); + setTransition({ + from: oldRoute ?? "/", + to: newRoute, + }); + setRoute(newRoute); } - function isActive(page: string) { - if (page === "/") return true; - const index = routeParts.indexOf(page); - if (index === -1) return false; // not active - if (index === routeParts.length - 1) return false; // active but latest route so shouldnt be counted as active - return true; + function showBackwardsTransition(path: string) { + if (!transition) return false; + const current = joinPath(splitPath(path, id)); + + if (current === transition.to && transition.from.startsWith(transition.to)) + return true; + if ( + current === transition.from && + transition.to.startsWith(transition.from) + ) + return true; + return false; } - function isCurrentPage(page: string) { - return routerActive && route === `/${id}${page}`; - } - - function isLoaded(page: string) { - if (page === "/") return true; - return route.includes(page); + function isCurrentPage(path: string) { + return routerActive && route === joinPath(splitPath(path, id)); } function isOverlayActive() { return routerActive; } - function pageProps(page: string) { - return { - show: isCurrentPage(page), - active: isActive(page), - }; - } - - function close() { + const close = useCallback(() => { + setTransition(null); setRoute(null); - } + }, [setRoute, setTransition]); - function open() { + const open = useCallback(() => { + setTransition(null); setRoute(`/${id}`); - } + }, [id, setRoute, setTransition]); return { + showBackwardsTransition, + isCurrentPage, isOverlayActive, navigate, close, - isLoaded, - isCurrentPage, - pageProps, - isActive, open, }; } + +export function useOverlayRouter(id: string) { + const router = useInternalOverlayRouter(id); + return { + open: router.open, + close: router.close, + navigate: router.navigate, + }; +} diff --git a/src/hooks/useQueryParams.ts b/src/hooks/useQueryParams.ts index 54c78b57..9be49a8c 100644 --- a/src/hooks/useQueryParams.ts +++ b/src/hooks/useQueryParams.ts @@ -16,11 +16,13 @@ export function useQueryParams() { return queryParams; } -export function useQueryParam(param: string) { +export function useQueryParam( + param: string +): [string | null, (a: string | null) => void] { const params = useQueryParams(); const location = useLocation(); const router = useHistory(); - const currentValue = params[param]; + const currentValue = params[param] ?? null; const set = useCallback( (value: string | null) => { @@ -34,5 +36,5 @@ export function useQueryParam(param: string) { [param, location, router] ); - return [currentValue, set] as const; + return [currentValue, set]; } diff --git a/src/pages/developer/TestView.tsx b/src/pages/developer/TestView.tsx index c796b502..4c64406f 100644 --- a/src/pages/developer/TestView.tsx +++ b/src/pages/developer/TestView.tsx @@ -6,7 +6,6 @@ import { useOverlayRouter } from "@/hooks/useOverlayRouter"; // simple empty view, perfect for putting in tests export default function TestView() { const router = useOverlayRouter("test"); - const pages = ["", "/one", "/two"]; return ( @@ -19,21 +18,57 @@ export default function TestView() { > Open -
- Home - Page one - Page two + +
+

HOME

+ + +
+
+ +
+

ONE

+ +
+
+ +
+

TWO

+ +
+
diff --git a/src/stores/overlay/store.ts b/src/stores/overlay/store.ts new file mode 100644 index 00000000..b019b53c --- /dev/null +++ b/src/stores/overlay/store.ts @@ -0,0 +1,23 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; + +export interface OverlayTransition { + from: string; + to: string; +} + +interface OverlayStore { + transition: null | OverlayTransition; + setTransition(newTrans: OverlayTransition | null): void; +} + +export const useOverlayStore = create( + immer((set) => ({ + transition: null, + setTransition(newTrans) { + set((s) => { + s.transition = newTrans; + }); + }, + })) +);