"use client"; // Adapted from Magic UI (MIT). See THIRD_PARTY_NOTICES.md. import { type ComponentProps, type RefObject, useEffect, useImperativeHandle, useRef, } from "react"; export interface ScrollProgressProps extends ComponentProps<"div"> { containerRef?: RefObject; } export function ScrollProgress({ containerRef, className = "", "aria-label": label = "Reading progress", ref: forwardedRef, ...props }: ScrollProgressProps) { const ref = useRef(null); useImperativeHandle(forwardedRef, () => ref.current!, []); useEffect(() => { const container = containerRef?.current; const target = container ?? window; let frame = 0; const update = () => { frame = 0; const element = container ?? document.documentElement; const height = element.scrollHeight - element.clientHeight; const progress = height > 0 ? Math.max(0, Math.min(1, element.scrollTop / height)) : 1; ref.current?.style.setProperty("--scroll-progress-value", `${progress}`); ref.current?.setAttribute( "aria-valuenow", `${Math.round(progress * 100)}`, ); }; const schedule = () => { if (!frame) frame = requestAnimationFrame(update); }; const observer = new ResizeObserver(schedule); observer.observe(container ?? document.documentElement); observer.observe(container?.firstElementChild ?? document.body); target.addEventListener("scroll", schedule, { passive: true }); window.addEventListener("resize", schedule); update(); return () => { cancelAnimationFrame(frame); observer.disconnect(); target.removeEventListener("scroll", schedule); window.removeEventListener("resize", schedule); }; }, [containerRef]); return (
); }