Download src/components/shared/PageTransitionLoader.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 4.4 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/PageTransitionLoader.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/shared/PageTransitionLoader.tsx
-
curl -L -o PageTransitionLoader.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/shared/PageTransitionLoader.tsx
4.4 kB
| "use client"; | |
| import { | |
| Suspense, | |
| useCallback, | |
| useEffect, | |
| useRef, | |
| useState, | |
| type MutableRefObject, | |
| } from "react"; | |
| import { usePathname, useSearchParams } from "next/navigation"; | |
| import { motion, AnimatePresence, useReducedMotion } from "framer-motion"; | |
| const REVEAL_DELAY_MS = 120; | |
| const COMPLETION_MS = 140; | |
| const FAILSAFE_MS = 10_000; | |
| type LoaderPhase = "idle" | "pending" | "completing"; | |
| function clearTimer(timer: MutableRefObject<number | null>) { | |
| if (timer.current === null) return; | |
| window.clearTimeout(timer.current); | |
| timer.current = null; | |
| } | |
| export function isSameDocumentNavigation(anchor: HTMLAnchorElement): boolean { | |
| if (anchor.target && anchor.target !== "_self") return false; | |
| if (anchor.hasAttribute("download")) return false; | |
| const next = new URL(anchor.href, window.location.href); | |
| const current = new URL(window.location.href); | |
| if (next.origin !== current.origin) return false; | |
| return `${next.pathname}${next.search}` !== `${current.pathname}${current.search}`; | |
| } | |
| function PageTransitionLoaderInner() { | |
| const pathname = usePathname(); | |
| const searchParams = useSearchParams(); | |
| const routeKey = `${pathname}?${searchParams.toString()}`; | |
| const reduceMotion = useReducedMotion(); | |
| const [phase, setPhase] = useState<LoaderPhase>("idle"); | |
| const isFirstPaint = useRef(true); | |
| const navigationArmed = useRef(false); | |
| const revealTimer = useRef<number | null>(null); | |
| const completionTimer = useRef<number | null>(null); | |
| const failsafeTimer = useRef<number | null>(null); | |
| const beginNavigation = useCallback(() => { | |
| clearTimer(revealTimer); | |
| clearTimer(completionTimer); | |
| clearTimer(failsafeTimer); | |
| navigationArmed.current = true; | |
| revealTimer.current = window.setTimeout(() => { | |
| setPhase("pending"); | |
| revealTimer.current = null; | |
| }, REVEAL_DELAY_MS); | |
| failsafeTimer.current = window.setTimeout(() => { | |
| navigationArmed.current = false; | |
| setPhase("idle"); | |
| failsafeTimer.current = null; | |
| }, FAILSAFE_MS); | |
| }, []); | |
| useEffect(() => { | |
| function handleDocumentClick(event: MouseEvent) { | |
| if ( | |
| event.defaultPrevented || | |
| event.button !== 0 || | |
| event.metaKey || | |
| event.ctrlKey || | |
| event.shiftKey || | |
| event.altKey | |
| ) { | |
| return; | |
| } | |
| const target = event.target; | |
| if (!(target instanceof Element)) return; | |
| const anchor = target.closest<HTMLAnchorElement>("a[href]"); | |
| if (!anchor || !isSameDocumentNavigation(anchor)) return; | |
| beginNavigation(); | |
| } | |
| window.addEventListener("popstate", beginNavigation); | |
| document.addEventListener("click", handleDocumentClick, true); | |
| return () => { | |
| window.removeEventListener("popstate", beginNavigation); | |
| document.removeEventListener("click", handleDocumentClick, true); | |
| clearTimer(revealTimer); | |
| clearTimer(completionTimer); | |
| clearTimer(failsafeTimer); | |
| }; | |
| }, [beginNavigation]); | |
| useEffect(() => { | |
| if (isFirstPaint.current) { | |
| isFirstPaint.current = false; | |
| return; | |
| } | |
| if (!navigationArmed.current) return; | |
| navigationArmed.current = false; | |
| clearTimer(revealTimer); | |
| clearTimer(failsafeTimer); | |
| setPhase("completing"); | |
| completionTimer.current = window.setTimeout(() => { | |
| setPhase("idle"); | |
| completionTimer.current = null; | |
| }, COMPLETION_MS); | |
| return () => clearTimer(completionTimer); | |
| }, [routeKey]); | |
| return ( | |
| <AnimatePresence> | |
| {phase !== "idle" ? ( | |
| <motion.div | |
| className="pointer-events-none fixed inset-x-0 top-0 z-[120] h-[2px] origin-left bg-[#171716]" | |
| initial={reduceMotion ? false : { scaleX: 0.08, opacity: 0 }} | |
| animate={ | |
| phase === "completing" | |
| ? { scaleX: 1, opacity: 1 } | |
| : reduceMotion | |
| ? { scaleX: 0.72, opacity: 0.72 } | |
| : { scaleX: 0.72, opacity: 1 } | |
| } | |
| exit={{ opacity: 0 }} | |
| transition={{ | |
| duration: reduceMotion ? 0 : phase === "completing" ? 0.12 : 0.8, | |
| ease: [0.16, 1, 0.3, 1], | |
| }} | |
| data-testid="page-transition-loader" | |
| aria-hidden | |
| /> | |
| ) : null} | |
| </AnimatePresence> | |
| ); | |
| } | |
| export function PageTransitionLoader() { | |
| return ( | |
| <Suspense fallback={null}> | |
| <PageTransitionLoaderInner /> | |
| </Suspense> | |
| ); | |
| } | |