UNPKG

next-navigation-progress

Version:

A customizable navigation progress bar for Next.js apps with React 19 support

200 lines (196 loc) 6.39 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var navigation = require('next/navigation'); var Link = require('next/link'); var react = require('react'); function easeOutQuad(t) { return t * (2 - t); } function generateEaseOutArray(length) { const result = []; for (let i = 1; i <= length; i++) { const easedValue = Math.round(easeOutQuad(i / (length - 1)) * 100); result.push(easedValue); } return result; } const Context = react.createContext({ progress: 0, startNewProgress: () => { }, optimisticObj: { loading: false }, stateObj: { showing: false }, }); /** * Internal hook for managing progress state * @internal */ const useProgress = () => { const [optimisticObj, setOptimisticObj] = react.useOptimistic({ loading: false, }); const [stateObj, setStateObj] = react.useState({ showing: false, }); // to show latest status, use obj instead of just boolean const [progress, setProgress] = react.useState(5); react.useEffect(() => { if (!optimisticObj.loading) { return; } setStateObj(() => { return { showing: true, }; }); setProgress(0); }, [optimisticObj]); react.useEffect(() => { if (optimisticObj.loading || !stateObj.showing) { return; } const timeout = setTimeout(() => { setProgress(0); setStateObj((obj) => { return { ...obj, showing: false, }; }); }, 300); return () => { clearTimeout(timeout); }; }, [optimisticObj.loading, stateObj.showing]); react.useEffect(() => { if (!stateObj.showing) { return; } let index = 0; const arr = generateEaseOutArray(100).map((val) => val * (90 / 100)); const updateProgress = () => { setProgress((prev) => { if (!optimisticObj.loading) { return 100; } const val = arr[index]; index++; if (!val) { return prev; } return val; }); }; const interval = setInterval(updateProgress, 100); updateProgress(); return () => { clearInterval(interval); }; }, [stateObj, optimisticObj.loading]); const startNewProgress = () => { setOptimisticObj({ loading: true, }); }; return { startNewProgress, progress, optimisticObj, stateObj, }; }; /** * Provider component that manages navigation progress state * Wrap your application with this provider to enable navigation progress functionality * * @example * ```tsx * <NextNavigationProgressProvider> * <NextNavigationProgressBar /> * {children} * </NextNavigationProgressProvider> * ``` */ const NextNavigationProgressProvider = ({ children, }) => { const { progress, startNewProgress, optimisticObj, stateObj } = useProgress(); return (jsxRuntime.jsx(Context.Provider, { value: { progress, startNewProgress, optimisticObj, stateObj }, children: children })); }; /** * Progress bar component that displays navigation progress * Must be used inside NextNavigationProgressProvider * * @example * ```tsx * <NextNavigationProgressProvider> * <NextNavigationProgressBar /> * {children} * </NextNavigationProgressProvider> * ``` */ const NextNavigationProgressBar = () => { const { progress, stateObj } = react.useContext(Context); return (jsxRuntime.jsx("div", { style: { display: stateObj.showing ? 'block' : 'none', }, children: jsxRuntime.jsx("div", { style: { width: stateObj.showing ? `${progress}%` : 0, height: '3px', backgroundColor: '#228be6', transition: stateObj.showing ? 'width 0.3s' : 'none', opacity: stateObj.showing ? 1 : 0, } }) })); }; /** * Hook to access navigation progress state and controls * Must be used inside NextNavigationProgressProvider * * @returns {Object} Progress state and control functions * @returns {number} progress - Current progress value (0-100) * @returns {Function} startNewProgress - Function to start a new progress animation * @returns {Object} optimisticObj - Object containing loading state * @returns {Object} stateObj - Object containing visibility state * * @example * ```tsx * const { progress, startNewProgress, optimisticObj, stateObj } = useNavigationProgress(); * ``` */ const useNavigationProgress = () => { return react.useContext(Context); }; /** * Pre-configured Link component that automatically triggers navigation progress * Wraps Next.js Link component with progress bar integration * * @param {React.ReactNode} children - Link content * @param {string} className - CSS class name * @param {string} href - Destination URL * @param {string} [target] - Link target attribute * @param {Function} [onClick] - Custom click handler * @param {boolean} [prefetch] - Next.js prefetch behavior * @param {React.CSSProperties} [style] - Inline styles * * @example * ```tsx * <NavigationLink href="/about" className="nav-link"> * About Page * </NavigationLink> * ``` */ const NavigationLink = react.memo(function NavigationLink({ children, className, href, target, onClick, prefetch, style, }) { const router = navigation.useRouter(); const { startNewProgress } = useNavigationProgress(); return (jsxRuntime.jsx(Link, { prefetch: prefetch, className: className, href: href, onClick: (e) => { if (onClick) { onClick(e); return; } react.startTransition(async () => { startNewProgress(); router.push(href); }); }, target: target, style: style, children: children })); }); exports.NavigationLink = NavigationLink; exports.NextNavigationProgressBar = NextNavigationProgressBar; exports.NextNavigationProgressProvider = NextNavigationProgressProvider; exports.useNavigationProgress = useNavigationProgress; exports.useProgress = useProgress; //# sourceMappingURL=index.js.map