{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "loading-bar",
  "title": "Loading Bar",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/loading-bar/LoadingBar.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface LoadingBarHandle {\n  start: (from?: number) => void\n  finish: () => void\n  error: () => void\n  inc: (amount?: number) => void\n  set: (value: number) => void\n}\n\nexport interface LoadingBarProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n  /** 0–100 progress value. Use with value/onChange or drive via the hook. */\n  value?: number\n  /** Bar color. Accepts any CSS color value. */\n  color?: string\n  /** Bar height in px. */\n  height?: number\n  /** Indeterminate sliding animation (ignores value). */\n  indeterminate?: boolean\n  /** Anchor the bar to the top or bottom of the viewport. */\n  position?: 'top' | 'bottom'\n  /** Show a spinner at the trailing edge of the bar. */\n  spinner?: boolean\n  /** Error state tints the bar. */\n  error?: boolean\n  /** Hide the bar entirely (e.g. when finished). */\n  hidden?: boolean\n  /** Fired with the new value on every internal update (v-model equivalent). */\n  onValueChange?: (value: number) => void\n  /** Fired when progress reaches 100. */\n  onFinish?: () => void\n}\n\nconst LoadingBar = React.forwardRef<LoadingBarHandle, LoadingBarProps>(\n  (\n    {\n      value = 0,\n      color = '',\n      height = 3,\n      indeterminate = false,\n      position = 'top',\n      spinner = false,\n      error = false,\n      hidden = false,\n      className,\n      onValueChange,\n      onFinish,\n      ...props\n    },\n    ref,\n  ) => {\n    const [internal, setInternal] = React.useState(value)\n    const rafRef = React.useRef<number | null>(null)\n    const onValueChangeRef = React.useRef(onValueChange)\n    const onFinishRef = React.useRef(onFinish)\n    onValueChangeRef.current = onValueChange\n    onFinishRef.current = onFinish\n\n    // Keep internal in sync when the controlled value prop changes.\n    React.useEffect(() => {\n      setInternal(value)\n    }, [value])\n\n    const set = React.useCallback((v: number) => {\n      setInternal(v)\n      onValueChangeRef.current?.(v)\n      if (v >= 100) onFinishRef.current?.()\n    }, [])\n\n    const tick = React.useCallback(\n      (target: number) => {\n        if (rafRef.current) cancelAnimationFrame(rafRef.current)\n        const step = () => {\n          setInternal((prev) => {\n            if (prev >= 100 || prev >= target) return prev\n            const next = Math.min(target, prev + (100 - prev) * 0.02 + 0.5)\n            onValueChangeRef.current?.(next)\n            if (next >= 100) onFinishRef.current?.()\n            if (next < target) rafRef.current = requestAnimationFrame(step)\n            return next\n          })\n        }\n        rafRef.current = requestAnimationFrame(step)\n      },\n      [],\n    )\n\n    const start = React.useCallback(\n      (from = 20) => {\n        set(from)\n        tick(from)\n      },\n      [set, tick],\n    )\n\n    const inc = React.useCallback((amount = 10) => {\n      setInternal((prev) => {\n        const next = Math.min(99, prev + amount)\n        onValueChangeRef.current?.(next)\n        return next\n      })\n    }, [])\n\n    const finish = React.useCallback(() => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current)\n      set(100)\n    }, [set])\n\n    const fail = React.useCallback(() => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current)\n      setInternal(100)\n      // error prop drives the color; emit finish so callers can hide.\n      onValueChangeRef.current?.(100)\n      onFinishRef.current?.()\n    }, [])\n\n    React.useEffect(() => {\n      return () => {\n        if (rafRef.current) cancelAnimationFrame(rafRef.current)\n      }\n    }, [])\n\n    React.useImperativeHandle(ref, () => ({ start, finish, error: fail, fail, inc, set }), [\n      start,\n      finish,\n      fail,\n      inc,\n      set,\n    ])\n\n    const pct = Math.min(100, Math.max(0, internal))\n    const barColor = color || (error ? 'var(--destructive)' : 'var(--primary)')\n    const visible = !hidden && (indeterminate || internal > 0)\n\n    return (\n      <div\n        data-uipkge=\"\"\n        data-slot=\"loading-bar\"\n        data-position={position}\n        data-state={error ? 'error' : indeterminate ? 'indeterminate' : 'determinate'}\n        className={cn(\n          'pointer-events-none fixed left-0 z-[9999] w-full transition-opacity duration-300',\n          position === 'top' ? 'top-0' : 'bottom-0',\n          visible ? 'opacity-100' : 'opacity-0',\n          className,\n        )}\n        style={{ height: `${height}px`, ...(props.style ?? {}) }}\n        role=\"progressbar\"\n        aria-valuemin={0}\n        aria-valuemax={100}\n        aria-valuenow={indeterminate ? undefined : pct}\n        aria-hidden={!visible}\n        {...props}\n      >\n        {/* Track */}\n        <div className=\"absolute inset-0 bg-transparent\" />\n\n        {indeterminate ? (\n          // Indeterminate sliding bar\n          <div\n            data-slot=\"loading-bar-indeterminate\"\n            className=\"loading-bar-indeterminate absolute inset-y-0 w-1/3\"\n            style={{ backgroundColor: barColor }}\n          >\n            {spinner && (\n              <div\n                data-slot=\"loading-bar-spinner\"\n                className=\"absolute top-1/2 right-0 size-3 translate-x-1/2 -translate-y-1/2 animate-spin rounded-full border-2 border-current border-t-transparent\"\n                style={{ color: barColor }}\n              />\n            )}\n          </div>\n        ) : (\n          // Determinate bar\n          <div\n            data-slot=\"loading-bar-fill\"\n            className=\"absolute inset-y-0 left-0 transition-[width] duration-200 ease-out\"\n            style={{ width: `${pct}%`, backgroundColor: barColor }}\n          >\n            {spinner && (\n              <div\n                data-slot=\"loading-bar-spinner\"\n                className=\"absolute top-1/2 right-0 size-3 translate-x-1/2 -translate-y-1/2 animate-spin rounded-full border-2 border-current border-t-transparent\"\n                style={{ color: barColor }}\n              />\n            )}\n          </div>\n        )}\n\n        <style>{`\n@media (prefers-reduced-motion: no-preference) {\n  .loading-bar-indeterminate {\n    animation: loading-bar-slide 1.2s ease-in-out infinite;\n  }\n}\n@keyframes loading-bar-slide {\n  0% { left: -33%; }\n  100% { left: 100%; }\n}\n`}</style>\n      </div>\n    )\n  },\n)\nLoadingBar.displayName = 'LoadingBar'\n\nexport { LoadingBar }\n",
      "type": "registry:ui",
      "target": "~/components/ui/loading-bar/LoadingBar.tsx"
    },
    {
      "path": "packages/registry-react/components/loading-bar/useLoadingBar.ts",
      "content": "'use client'\n\nimport * as React from 'react'\nimport type { LoadingBarHandle } from './LoadingBar'\n\nexport type { LoadingBarHandle }\n\n/**\n * Hook that drives a <LoadingBar> instance via a ref callback.\n *\n * Usage:\n *   const bar = useLoadingBar()\n *   <LoadingBar ref={bar.setRef} />\n *   bar.start()\n *   await fetch(...)\n *   bar.finish()\n */\nexport function useLoadingBar() {\n  const handleRef = React.useRef<LoadingBarHandle | null>(null)\n  const [loading, setLoading] = React.useState(false)\n  const [isError, setIsError] = React.useState(false)\n\n  const setRef = React.useCallback((el: LoadingBarHandle | null) => {\n    handleRef.current = el\n  }, [])\n\n  function start(from = 20) {\n    setIsError(false)\n    setLoading(true)\n    handleRef.current?.start(from)\n  }\n\n  function finish() {\n    setLoading(false)\n    handleRef.current?.finish()\n  }\n\n  function error() {\n    setIsError(true)\n    setLoading(false)\n    handleRef.current?.error()\n  }\n\n  function inc(amount = 10) {\n    handleRef.current?.inc(amount)\n  }\n\n  function set(value: number) {\n    handleRef.current?.set(value)\n  }\n\n  return {\n    setRef,\n    loading,\n    isError,\n    start,\n    finish,\n    error,\n    inc,\n    set,\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/components/ui/loading-bar/useLoadingBar.ts"
    },
    {
      "path": "packages/registry-react/components/loading-bar/index.ts",
      "content": "export { LoadingBar, type LoadingBarHandle, type LoadingBarProps } from './LoadingBar'\nexport { useLoadingBar } from './useLoadingBar'\n",
      "type": "registry:ui",
      "target": "~/components/ui/loading-bar/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Top-of-viewport NProgress-style progress bar. Drive it imperatively via the useLoadingBar hook (start/finish/error/inc) or with value/onChange. Supports indeterminate mode, custom color and height, top/bottom anchoring, and an optional trailing spinner.",
  "categories": [
    "feedback"
  ]
}