{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress-bar",
  "type": "registry:ui",
  "title": "Progress bar",
  "description": "Covers pending, determinate, and complete progress states.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "feedback"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/components/progress-bar/",
  "meta": {
    "engines": [
      "motion"
    ],
    "runtimeCost": "light",
    "signature": "Pending, progress, and completion resolve along one bar",
    "sceneFamily": "product-mono",
    "motionRole": "lively",
    "primaryState": "Task bar at a determinate progress value",
    "assetProvenance": "self-contained"
  },
  "files": [
    {
      "path": "src/registry/components/progress-bar.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/progress-bar.tsx",
      "content": "\"use client\";\n\nimport type { AriaAttributes } from \"react\";\nimport { useId } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\nconst FILL = { type: \"spring\", stiffness: 210, damping: 34, mass: 0.9 } as const;\nconst CROSSFADE = { type: \"spring\", stiffness: 260, damping: 34, mass: 0.8 } as const;\nconst INSTANT = { duration: 0 } as const;\n\nexport type ProgressBarProps = {\n  value: number | null;\n  max?: number;\n  label?: string;\n  pendingLabel?: string;\n  completeLabel?: string;\n  className?: string;\n};\n\nexport function ProgressBar({\n  value,\n  max = 100,\n  label = \"Progress\",\n  pendingLabel = \"Working\",\n  completeLabel = \"Complete\",\n  className = \"\",\n}: ProgressBarProps) {\n  const reduced = useReducedMotion();\n  const labelId = useId();\n\n  const indeterminate = value === null;\n  const fraction =\n    value === null || max <= 0 ? 0 : Math.min(1, Math.max(0, value / max));\n  const percent = Math.round(fraction * 100);\n  const complete = !indeterminate && fraction >= 1;\n\n  const measured: AriaAttributes = indeterminate\n    ? {}\n    : {\n        \"aria-valuenow\": Math.round(fraction * max * 100) / 100,\n        \"aria-valuetext\": `${percent}%`,\n      };\n\n  return (\n    <div className={`w-full ${className}`}>\n      <div className=\"flex items-baseline justify-between gap-3\">\n        <span\n          id={labelId}\n          className=\"truncate text-[13px] font-medium text-stone-700 dark:text-stone-200\"\n        >\n          {label}\n        </span>\n\n        <span\n          aria-hidden\n          className=\"grid shrink-0 justify-items-end text-stone-500 dark:text-stone-400\"\n        >\n          <motion.span\n            className=\"col-start-1 row-start-1 whitespace-nowrap text-[12px] font-medium leading-5\"\n            initial={false}\n            animate={{ opacity: indeterminate ? 1 : 0 }}\n            transition={reduced ? INSTANT : CROSSFADE}\n          >\n            {pendingLabel}\n          </motion.span>\n\n          <motion.span\n            className=\"col-start-1 row-start-1 whitespace-nowrap font-mono text-[12px] font-medium leading-5 tabular-nums\"\n            initial={false}\n            animate={{ opacity: indeterminate ? 0 : 1 }}\n            transition={reduced ? INSTANT : CROSSFADE}\n          >\n            {percent}%\n          </motion.span>\n        </span>\n      </div>\n\n      <div\n        role=\"progressbar\"\n        aria-labelledby={labelId}\n        aria-valuemin={0}\n        aria-valuemax={max}\n        {...measured}\n        className=\"mt-2 rounded-[4px] bg-stone-200/60 p-[2px] shadow-[inset_0_1px_2px_rgba(28,25,23,0.1),inset_0_0_0_1px_rgba(28,25,23,0.06)] dark:bg-[#1D1D1A] dark:shadow-[inset_0_1px_2px_rgba(0,0,0,0.45)]\"\n      >\n        <div className=\"relative h-[8px] overflow-hidden rounded-[2px]\">\n          <motion.span\n            aria-hidden\n            className=\"absolute inset-0 block origin-left rounded-[2px] bg-[#4568FF] shadow-[inset_0_1px_0_rgba(255,255,255,0.35),inset_0_-1px_0_rgba(28,25,23,0.2)] dark:bg-[#93B0FF] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.4),inset_0_-1px_0_rgba(0,0,0,0.25)]\"\n            initial={false}\n            animate={{ scaleX: indeterminate ? 0 : fraction }}\n            transition={reduced ? INSTANT : FILL}\n          />\n\n          {indeterminate && !reduced ? (\n            <motion.span\n              aria-hidden\n              className=\"absolute inset-y-0 left-0 block w-2/5 rounded-[2px] bg-[#4568FF] shadow-[inset_0_1px_0_rgba(255,255,255,0.35),inset_0_-1px_0_rgba(28,25,23,0.2)] dark:bg-[#93B0FF] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.4),inset_0_-1px_0_rgba(0,0,0,0.25)]\"\n              initial={{ x: \"-100%\", opacity: 0 }}\n              animate={{ x: \"250%\", opacity: 1 }}\n              exit={{ opacity: 0 }}\n              transition={{\n                x: { duration: 1.25, ease: \"easeInOut\", repeat: Infinity },\n                opacity: { duration: 0.18 },\n              }}\n            />\n          ) : null}\n        </div>\n      </div>\n\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {complete ? completeLabel : indeterminate ? pendingLabel : \"\"}\n      </span>\n    </div>\n  );\n}\n"
    }
  ]
}
