2026-06-02 12:38:01 +08:00
|
|
|
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
|
|
|
|
|
|
|
|
interface PageTransitionProps {
|
|
|
|
|
viewKey: string;
|
|
|
|
|
children: ReactNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EXIT_DURATION_MS = 180;
|
|
|
|
|
|
2026-06-02 18:31:39 +08:00
|
|
|
const NAV_ORDER: string[] = [
|
|
|
|
|
"home",
|
|
|
|
|
"workbench",
|
|
|
|
|
"ecommerce",
|
|
|
|
|
"ecommerceTemplates",
|
|
|
|
|
"sizeTemplate",
|
|
|
|
|
"canvas",
|
|
|
|
|
"scriptTokens",
|
|
|
|
|
"tokenUsage",
|
|
|
|
|
"community",
|
|
|
|
|
"assets",
|
|
|
|
|
"more",
|
|
|
|
|
"imageWorkbench",
|
|
|
|
|
"resolutionUpscale",
|
|
|
|
|
"watermarkRemoval",
|
|
|
|
|
"subtitleRemoval",
|
|
|
|
|
"digitalHuman",
|
|
|
|
|
"avatarConsole",
|
|
|
|
|
"characterMix",
|
|
|
|
|
"agent",
|
|
|
|
|
"settings",
|
|
|
|
|
"login",
|
|
|
|
|
"profile",
|
|
|
|
|
"report",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function getNavIndex(key: string): number {
|
|
|
|
|
return NAV_ORDER.indexOf(key);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 12:38:01 +08:00
|
|
|
export default function PageTransition({ viewKey, children }: PageTransitionProps) {
|
|
|
|
|
const [displayedChildren, setDisplayedChildren] = useState(children);
|
2026-06-02 22:19:14 +08:00
|
|
|
const [phase, setPhase] = useState<"idle" | "exit">("idle");
|
2026-06-02 19:37:29 +08:00
|
|
|
const [exitDirection, setExitDirection] = useState<"forward" | "backward" | "neutral">("neutral");
|
2026-06-02 12:38:01 +08:00
|
|
|
const prevKeyRef = useRef(viewKey);
|
|
|
|
|
const timerRef = useRef<ReturnType<typeof setTimeout>>();
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (viewKey === prevKeyRef.current) {
|
|
|
|
|
setDisplayedChildren(children);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-06-02 19:37:29 +08:00
|
|
|
|
|
|
|
|
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
|
|
|
if (prefersReducedMotion) {
|
|
|
|
|
prevKeyRef.current = viewKey;
|
|
|
|
|
setDisplayedChildren(children);
|
|
|
|
|
setPhase("idle");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 18:31:39 +08:00
|
|
|
const prevIndex = getNavIndex(prevKeyRef.current);
|
|
|
|
|
const nextIndex = getNavIndex(viewKey);
|
|
|
|
|
if (prevIndex < nextIndex) {
|
2026-06-02 19:37:29 +08:00
|
|
|
setExitDirection("forward");
|
2026-06-02 18:31:39 +08:00
|
|
|
} else if (prevIndex > nextIndex) {
|
2026-06-02 19:37:29 +08:00
|
|
|
setExitDirection("backward");
|
2026-06-02 18:31:39 +08:00
|
|
|
} else {
|
2026-06-02 19:37:29 +08:00
|
|
|
setExitDirection("neutral");
|
2026-06-02 18:31:39 +08:00
|
|
|
}
|
2026-06-02 12:38:01 +08:00
|
|
|
prevKeyRef.current = viewKey;
|
2026-06-02 19:37:29 +08:00
|
|
|
|
2026-06-02 12:38:01 +08:00
|
|
|
setPhase("exit");
|
|
|
|
|
timerRef.current = setTimeout(() => {
|
|
|
|
|
setDisplayedChildren(children);
|
2026-06-02 22:19:14 +08:00
|
|
|
setPhase("idle");
|
2026-06-02 12:38:01 +08:00
|
|
|
}, EXIT_DURATION_MS);
|
|
|
|
|
return () => clearTimeout(timerRef.current);
|
|
|
|
|
}, [viewKey, children]);
|
|
|
|
|
|
2026-06-02 19:37:29 +08:00
|
|
|
const dirClass = exitDirection === "forward" ? " is-forward" : exitDirection === "backward" ? " is-backward" : "";
|
|
|
|
|
|
2026-06-02 12:38:01 +08:00
|
|
|
return (
|
2026-06-02 22:19:14 +08:00
|
|
|
<div className={phase === "exit" ? `page-transition-wrap page-motion--exit${dirClass}` : "page-transition-wrap"}>
|
2026-06-02 12:38:01 +08:00
|
|
|
{displayedChildren}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-06-02 19:37:29 +08:00
|
|
|
}
|