{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"progress","type":"registry:ui","title":"Progress","description":"Linear and circular progress with a spring-animated value and counting label, a moving sheen, and indeterminate slide and spin states.","dependencies":["motion"],"registryDependencies":["utils"],"files":[{"path":"registry/ui/progress.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { motion, useReducedMotion, useSpring, useTransform } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst css = `\n@keyframes ui-progress-shine{from{translate:-100% 0}to{translate:300% 0}}\n@keyframes ui-progress-slide{0%{left:-40%;width:40%}60%{width:55%}100%{left:100%;width:30%}}\n@keyframes ui-progress-spin{to{rotate:360deg}}\n@keyframes ui-progress-arc{0%{stroke-dasharray:1 100;stroke-dashoffset:0}50%{stroke-dasharray:60 100;stroke-dashoffset:-15}100%{stroke-dasharray:60 100;stroke-dashoffset:-99}}\n.ui-progress-shine{animation:ui-progress-shine 2.2s cubic-bezier(.4,0,.2,1) infinite}\n.ui-progress-slide{animation:ui-progress-slide 1.4s cubic-bezier(.65,0,.35,1) infinite}\n.ui-progress-spin{animation:ui-progress-spin 1.4s linear infinite}\n.ui-progress-arc{animation:ui-progress-arc 1.4s ease-in-out infinite}\n@media (prefers-reduced-motion:reduce){.ui-progress-shine{animation:none;opacity:0}.ui-progress-slide,.ui-progress-spin,.ui-progress-arc{animation-duration:3s}}\n`;\n\nexport type ProgressSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface ProgressProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n  /** Current value (undefined counts as 0). `null` = indeterminate (unknown duration). */\n  value?: number | null;\n  max?: number;\n  variant?: \"linear\" | \"circular\";\n  size?: ProgressSize;\n  /** Visible label, also used as the accessible name. */\n  label?: ReactNode;\n  /** Show the percentage (animated count). */\n  showValue?: boolean;\n}\n\nconst bar: Record<ProgressSize, string> = { sm: \"h-1\", md: \"h-2\", lg: \"h-3\" };\nconst ring: Record<ProgressSize, { px: number; stroke: number; text: string }> = {\n  sm: { px: 36, stroke: 4, text: \"text-[10px]\" },\n  md: { px: 56, stroke: 5, text: \"text-xs\" },\n  lg: { px: 88, stroke: 7, text: \"text-base\" },\n};\n\nexport function Progress({\n  value,\n  max = 100,\n  variant = \"linear\",\n  size = \"md\",\n  label,\n  showValue = true,\n  className,\n  ...props\n}: ProgressProps) {\n  const id = useId();\n  const indeterminate = value === null;\n  const pct = indeterminate ? 0 : Math.min(100, Math.max(0, ((value ?? 0) / max) * 100));\n  const reduce = useReducedMotion();\n  const spring = useSpring(0, { stiffness: 90, damping: 18, mass: 0.8 });\n  useEffect(() => {\n    if (reduce) spring.jump(pct);\n    else spring.set(pct);\n  }, [pct, reduce, spring]);\n  const text = useTransform(spring, (v) => `${Math.round(v)}%`);\n  const x = useTransform(spring, (v) => `${v - 100}%`);\n  const offset = useTransform(spring, (v) => 100 - v);\n\n  const aria = {\n    role: \"progressbar\",\n    \"aria-valuemin\": 0,\n    \"aria-valuemax\": max,\n    \"aria-valuenow\": indeterminate ? undefined : (value ?? 0),\n    \"aria-valuetext\": indeterminate ? \"Loading\" : `${Math.round(pct)}%`,\n    \"aria-labelledby\": label ? `${id}-label` : undefined,\n    \"aria-busy\": indeterminate || undefined,\n  } as const;\n\n  const style = (\n    <style href=\"ui-progress\" precedence=\"default\">\n      {css}\n    </style>\n  );\n\n  if (variant === \"circular\") {\n    const { px, stroke, text: textSize } = ring[size];\n    const r = (px - stroke) / 2;\n    return (\n      <div {...props} className={cn(\"inline-flex flex-col items-center gap-2\", className)}>\n        {style}\n        <div {...aria} className=\"relative grid place-items-center\" style={{ width: px, height: px }}>\n          <svg aria-hidden viewBox={`0 0 ${px} ${px}`} className={cn(\"absolute inset-0 -rotate-90\", indeterminate && \"ui-progress-spin\")}>\n            <circle cx={px / 2} cy={px / 2} r={r} fill=\"none\" strokeWidth={stroke} className=\"stroke-muted\" />\n            <motion.circle\n              cx={px / 2}\n              cy={px / 2}\n              r={r}\n              fill=\"none\"\n              strokeWidth={stroke}\n              strokeLinecap=\"round\"\n              pathLength={100}\n              strokeDasharray=\"100 100\"\n              className={cn(\n                \"stroke-primary drop-shadow-[0_0_6px_color-mix(in_oklab,var(--primary)_45%,transparent)]\",\n                indeterminate && \"ui-progress-arc\",\n              )}\n              style={indeterminate ? undefined : { strokeDashoffset: offset }}\n            />\n          </svg>\n          {showValue && !indeterminate && (\n            <motion.span aria-hidden className={cn(\"relative font-medium tabular-nums text-foreground\", textSize)}>\n              {text}\n            </motion.span>\n          )}\n        </div>\n        {label && (\n          <span id={`${id}-label`} className=\"text-sm text-muted-foreground\">\n            {label}\n          </span>\n        )}\n      </div>\n    );\n  }\n\n  return (\n    <div {...props} className={cn(\"grid w-full gap-2\", className)}>\n      {style}\n      {(label || (showValue && !indeterminate)) && (\n        <div className=\"flex items-baseline justify-between gap-4 text-sm\">\n          <span id={`${id}-label`} className=\"font-medium text-foreground\">\n            {label}\n          </span>\n          {showValue && !indeterminate && (\n            <motion.span aria-hidden className=\"tabular-nums text-muted-foreground\">\n              {text}\n            </motion.span>\n          )}\n        </div>\n      )}\n      <div {...aria} className={cn(\"relative w-full overflow-hidden rounded-full bg-muted\", bar[size])}>\n        {indeterminate ? (\n          <span className=\"ui-progress-slide absolute inset-y-0 rounded-full bg-primary\" />\n        ) : (\n          <motion.div className=\"absolute inset-0 overflow-hidden rounded-full bg-primary\" style={{ x }}>\n            <span className=\"ui-progress-shine absolute inset-y-0 left-0 w-1/3 bg-linear-to-r from-transparent via-white/45 to-transparent\" />\n          </motion.div>\n        )}\n      </div>\n    </div>\n  );\n}\n"}]}