{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"page-loading-bar","type":"registry:component","title":"Page Loading Bar","description":"An nprogress-style top-of-page bar that eases toward completion, never stalling.","dependencies":["framer-motion"],"registryDependencies":[],"files":[{"path":"components/viberdy/page-loading-bar.tsx","content":"\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { motion } from \"framer-motion\";\n\nexport function usePageLoadingBar() {\n  const [progress, setProgress] = useState(0);\n  const [loading, setLoading] = useState(false);\n  const rafRef = useRef<number | null>(null);\n\n  function start() {\n    setLoading(true);\n    setProgress(3);\n  }\n\n  useEffect(() => {\n    if (!loading) return;\n    function tick() {\n      setProgress((p) => (p >= 90 ? p : p + (90 - p) * 0.03));\n      rafRef.current = requestAnimationFrame(tick);\n    }\n    rafRef.current = requestAnimationFrame(tick);\n    return () => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n    };\n  }, [loading]);\n\n  function finish() {\n    if (rafRef.current) cancelAnimationFrame(rafRef.current);\n    setProgress(100);\n    setTimeout(() => {\n      setLoading(false);\n      setProgress(0);\n    }, 300);\n  }\n\n  return { progress, loading, start, finish };\n}\n\nexport function PageLoadingBar({ progress, loading, color = \"#ff2d2d\" }: { progress: number; loading: boolean; color?: string }) {\n  return (\n    <div\n      role=\"progressbar\"\n      aria-valuenow={Math.round(progress)}\n      aria-valuemin={0}\n      aria-valuemax={100}\n      aria-label=\"Page loading progress\"\n      className=\"fixed inset-x-0 top-0 z-50 h-1 bg-transparent\"\n    >\n      <motion.div\n        animate={{ width: `${progress}%`, opacity: loading || progress > 0 ? 1 : 0 }}\n        transition={{ duration: 0.2, ease: \"easeOut\" }}\n        className=\"h-full\"\n        style={{ background: color }}\n      />\n    </div>\n  );\n}\n","type":"registry:component","target":"components/viberdy/page-loading-bar.tsx"}],"meta":{"tier":"free","tags":["loading","progress","nprogress","navigation"],"href":"https://viberdy.dev/library/page-loading-bar","aiPrompt":"Create a React + Tailwind + Framer Motion \"top-of-page loading bar\" (nprogress-style) pattern: a `usePageLoadingBar` hook plus a `PageLoadingBar` display component.\n\nBehavior:\n- The hook exposes { progress, loading, start, finish }. Calling `start()` sets loading true and progress to a small initial value (e.g. 3%).\n- While loading, a requestAnimationFrame loop advances progress toward ~90% with an EASING approach — each frame moves progress a fraction of the remaining distance to 90 (e.g. `p + (90 - p) * 0.03`), which decelerates asymptotically and never actually reaches 90 on its own. This is the standard trick for indeterminate-duration loading bars: it always looks like it's still making progress without ever appearing finished or stuck.\n- Calling `finish()` cancels the RAF loop, snaps progress to 100, then after a brief pause (~300ms, enough to see the bar completing) resets loading to false and progress to 0.\n- The display component is a fixed, full-width, very thin bar pinned to the very top of the viewport, whose width and opacity are driven by the progress/loading values from the hook.\n- Wire `start()` to fire on route-change-start (e.g. a Next.js router event or a manual navigation trigger) and `finish()` when the new page has rendered.\n\nStyle: a thin (2-4px) full-width bar in a bold accent color, fixed to the very top edge, no border-radius needed at that thinness.\n\nOutput self-contained \"use client\" code (hook + component).","license":"https://viberdy.dev/license"}}