{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "loading-bar",
  "title": "Loading Bar",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/loading-bar/LoadingBar.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n  defineProps<{\n    /** 0–100 progress value. Use with v-model or drive via the composable. */\n    modelValue?: 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 modelValue). */\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    class?: HTMLAttributes['class']\n  }>(),\n  {\n    modelValue: 0,\n    color: '',\n    height: 3,\n    indeterminate: false,\n    position: 'top',\n    spinner: false,\n    error: false,\n    hidden: false,\n  },\n)\n\nconst emit = defineEmits<{\n  (e: 'update:modelValue', value: number): void\n  (e: 'finish'): void\n}>()\n\nconst internal = ref(props.modelValue)\nlet raf: number | null = null\n\nwatch(\n  () => props.modelValue,\n  (v) => {\n    internal.value = v\n  },\n)\n\nconst pct = computed(() => Math.min(100, Math.max(0, internal.value)))\nconst barColor = computed(() => props.color || (props.error ? 'var(--destructive)' : 'var(--primary)'))\nconst visible = computed(() => !props.hidden && (props.indeterminate || internal.value > 0))\n\nfunction set(v: number) {\n  internal.value = v\n  emit('update:modelValue', v)\n  if (v >= 100) emit('finish')\n}\n\n// Imperative API exposed for the composable / parent to drive the bar.\nfunction start(from = 20) {\n  set(from)\n  tick(from)\n}\n\nfunction tick(target: number) {\n  if (raf) cancelAnimationFrame(raf)\n  const step = () => {\n    if (internal.value >= 100 || internal.value >= target) return\n    const next = Math.min(target, internal.value + (100 - internal.value) * 0.02 + 0.5)\n    set(next)\n    if (next < target) raf = requestAnimationFrame(step)\n  }\n  raf = requestAnimationFrame(step)\n}\n\nfunction inc(amount = 10) {\n  set(Math.min(99, internal.value + amount))\n}\n\nfunction finish() {\n  if (raf) cancelAnimationFrame(raf)\n  set(100)\n}\n\nfunction fail() {\n  if (raf) cancelAnimationFrame(raf)\n  internal.value = 100\n  // error prop drives the color; emit finish so callers can hide.\n  emit('update:modelValue', 100)\n  emit('finish')\n}\n\nonBeforeUnmount(() => {\n  if (raf) cancelAnimationFrame(raf)\n})\n\ndefineExpose({ start, finish, fail, error: fail, inc, set })\n</script>\n\n<template>\n  <div\n    data-uipkge\n    data-slot=\"loading-bar\"\n    :data-position=\"position\"\n    :data-state=\"error ? 'error' : indeterminate ? 'indeterminate' : 'determinate'\"\n    :class=\"\n      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        props.class,\n      )\n    \"\n    :style=\"{ height: `${height}px` }\"\n    role=\"progressbar\"\n    :aria-valuemin=\"0\"\n    :aria-valuemax=\"100\"\n    :aria-valuenow=\"indeterminate ? undefined : pct\"\n    :aria-hidden=\"!visible\"\n  >\n    <!-- Track -->\n    <div class=\"absolute inset-0 bg-transparent\" />\n\n    <!-- Determinate bar -->\n    <div\n      v-if=\"!indeterminate\"\n      data-slot=\"loading-bar-fill\"\n      class=\"absolute inset-y-0 left-0 transition-[width] duration-200 ease-out\"\n      :style=\"{ width: `${pct}%`, backgroundColor: barColor }\"\n    >\n      <div\n        v-if=\"spinner\"\n        data-slot=\"loading-bar-spinner\"\n        class=\"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    </div>\n\n    <!-- Indeterminate sliding bar -->\n    <div\n      v-else\n      data-slot=\"loading-bar-indeterminate\"\n      class=\"loading-bar-indeterminate absolute inset-y-0 w-1/3\"\n      :style=\"{ backgroundColor: barColor }\"\n    >\n      <div\n        v-if=\"spinner\"\n        data-slot=\"loading-bar-spinner\"\n        class=\"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    </div>\n  </div>\n</template>\n\n<style scoped>\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\n@keyframes loading-bar-slide {\n  0% {\n    left: -33%;\n  }\n  100% {\n    left: 100%;\n  }\n}\n</style>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/loading-bar/LoadingBar.vue"
    },
    {
      "path": "packages/registry-vue/components/loading-bar/useLoadingBar.ts",
      "content": "import { ref, shallowRef } from 'vue'\nimport type { ComponentPublicInstance } from 'vue'\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\n/**\n * Composable that drives a <LoadingBar> instance via a template ref.\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 refEl = shallowRef<ComponentPublicInstance | null>(null)\n  const loading = ref(false)\n  const isError = ref(false)\n\n  function setRef(el: Element | ComponentPublicInstance | null) {\n    refEl.value = el as ComponentPublicInstance | null\n  }\n\n  function getHandle(): LoadingBarHandle | null {\n    return (refEl.value as unknown as LoadingBarHandle) ?? null\n  }\n\n  function start(from = 20) {\n    isError.value = false\n    loading.value = true\n    const h = getHandle()\n    if (h && typeof h.start === 'function') h.start(from)\n  }\n\n  function finish() {\n    loading.value = false\n    const h = getHandle()\n    if (h && typeof h.finish === 'function') h.finish()\n  }\n\n  function error() {\n    isError.value = true\n    loading.value = false\n    const h = getHandle()\n    if (h && typeof h.error === 'function') h.error()\n  }\n\n  function inc(amount = 10) {\n    const h = getHandle()\n    if (h && typeof h.inc === 'function') h.inc(amount)\n  }\n\n  function set(value: number) {\n    const h = getHandle()\n    if (h && typeof h.set === 'function') h.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": "~/app/components/ui/loading-bar/useLoadingBar.ts"
    },
    {
      "path": "packages/registry-vue/components/loading-bar/index.ts",
      "content": "export { default as LoadingBar } from './LoadingBar.vue'\nexport { useLoadingBar, type LoadingBarHandle } from './useLoadingBar'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/loading-bar/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Top-of-viewport NProgress-style progress bar. Drive it imperatively via the useLoadingBar composable (start/finish/error/inc) or with v-model. Supports indeterminate mode, custom color and height, top/bottom anchoring, and an optional trailing spinner.",
  "categories": [
    "feedback"
  ]
}