{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"progress","type":"registry:ui","title":"Progress","description":"A measured bar for work with a known size, such as an upload.","dependencies":["clsx","lucide-react","tailwind-merge"],"registryDependencies":["https://caliperui.site/r/styles.json"],"files":[{"path":"ui/lib/cn.ts","type":"registry:ui","target":"components/ui/lib/cn.ts","content":"import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n  extend: { theme: { radius: [\"control\", \"surface\", \"overlay\"], shadow: [\"overlay\"] } },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"},{"path":"ui/lib/motion-tokens.ts","type":"registry:ui","target":"components/ui/lib/motion-tokens.ts","content":"/** Milliseconds from a CSS time such as \"280ms\" or \".28s\" (build tools may rewrite one as the other). */\nexport function cssMs(value: string) {\n  const text = value.trim();\n  const number = parseFloat(text);\n  if (!Number.isFinite(number)) return 0;\n  return text.endsWith(\"ms\") ? number : text.endsWith(\"s\") ? number * 1000 : number;\n}\n\n/** Reads a duration token (e.g. \"--duration-travel\") in milliseconds from an element; the tokens resolve to 0 under reduced motion. */\nexport function tokenMs(element: Element, name: string) {\n  return cssMs(getComputedStyle(element).getPropertyValue(name));\n}\n"},{"path":"ui/progress.tsx","type":"registry:ui","target":"components/ui/progress.tsx","content":"\"use client\";\n\nimport { CircleCheck } from \"lucide-react\";\nimport { useEffect, useRef, useState, type ComponentProps } from \"react\";\nimport { cn } from \"./lib/cn\";\nimport { tokenMs } from \"./lib/motion-tokens\";\nimport \"./shared/progress-fill.css\";\nimport \"./progress.css\";\n\nfunction settle(amount: number) {\n  const x1 = 0.22;\n  const y1 = 1.2;\n  const x2 = 0.36;\n  const y2 = 1;\n  let t = amount;\n  for (let step = 0; step < 6; step += 1) {\n    const u = 1 - t;\n    const x = 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t;\n    const dx = 3 * u * u * x1 + 6 * u * t * (x2 - x1) + 3 * t * t * (1 - x2);\n    if (Math.abs(x - amount) < 0.001 || dx === 0) break;\n    t = Math.min(1, Math.max(0, t - (x - amount) / dx));\n  }\n  const u = 1 - t;\n  return 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t;\n}\n\nfunction useSettled(target: number) {\n  const current = useRef(target);\n  const [shown, setShown] = useState(target);\n\n  useEffect(() => {\n    const from = current.current;\n    const duration = tokenMs(document.documentElement, \"--duration-deliberate\");\n    if (duration <= 0 || from === target) {\n      current.current = target;\n      setShown(target);\n      return;\n    }\n    const started = performance.now();\n    let frame = 0;\n    const tick = (now: number) => {\n      const amount = Math.min(1, (now - started) / duration);\n      const next = from + (target - from) * settle(amount);\n      current.current = next;\n      setShown(next);\n      if (amount < 1) frame = requestAnimationFrame(tick);\n    };\n    frame = requestAnimationFrame(tick);\n    return () => cancelAnimationFrame(frame);\n  }, [target]);\n\n  return shown;\n}\n\nexport function Progress({\n  value = 0,\n  max = 100,\n  label,\n  showValue = false,\n  className,\n  ...props\n}: Omit<ComponentProps<\"div\">, \"children\"> & {\n  value?: number;\n  max?: number;\n  label?: string;\n  showValue?: boolean;\n}) {\n  const limit = Number.isFinite(max) && max > 0 ? max : 100;\n  const current = Math.min(limit, Math.max(0, Number.isFinite(value) ? value : 0));\n  const target = (current / limit) * 100;\n  const shown = useSettled(target);\n  const complete = current >= limit;\n  const percent = Math.round(shown);\n\n  return (\n    <div className={cn(\"min-w-0\", className)}>\n      {(label || showValue) && (\n        <div className=\"mb-2 flex items-center justify-between gap-3\">\n          {label ? (\n            <p className=\"min-w-0 text-sm font-medium\">{label}</p>\n          ) : (\n            <span />\n          )}\n          {showValue && (\n            <p className=\"flex shrink-0 items-center gap-1 text-xs tabular-nums text-muted-foreground\">\n              <span>{percent}%</span>\n              {complete && (\n                <CircleCheck\n                  aria-hidden=\"true\"\n                  className=\"size-3.5 text-success\"\n                  data-slot=\"progress-check\"\n                />\n              )}\n            </p>\n          )}\n        </div>\n      )}\n      <div\n        role=\"progressbar\"\n        aria-valuemin={0}\n        aria-valuemax={limit}\n        aria-valuenow={current}\n        aria-valuetext={`${Math.round(target)}%`}\n        aria-label={label ?? \"Progress\"}\n        data-slot=\"progress\"\n        data-complete={complete}\n        className=\"h-2 overflow-hidden rounded-full bg-muted\"\n        {...props}\n      >\n        <span\n          data-slot=\"progress-fill\"\n          className=\"block h-full rounded-full bg-brand\"\n          style={{ width: `${shown}%` }}\n        />\n      </div>\n    </div>\n  );\n}\n"},{"path":"ui/shared/progress-fill.css","type":"registry:file","target":"components/ui/shared/progress-fill.css","content":"/* Shared by progress.tsx, usage-meter.tsx; each imports it. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  @media (prefers-reduced-motion: no-preference) {\n    :is([data-slot=\"progress-fill\"], [data-slot=\"usage-segment\"], [data-slot=\"usage-projected\"]) {\n      animation: caliper-chart-sweep var(--duration-showcase) var(--ease-showcase) backwards;\n    }\n  }\n}\n"},{"path":"ui/progress.css","type":"registry:file","target":"components/ui/progress.css","content":"/* progress.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"progress-fill\"] {\n    transition: background-color var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"progress\"][data-complete=\"true\"] [data-slot=\"progress-fill\"] {\n    background: var(--success);\n  }\n  [data-slot=\"progress-check\"] {\n    animation: caliper-feedback-symbol-enter var(--duration-normal) var(--ease-settle);\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"progress-check\"] {\n      animation: none;\n      transition: none;\n      transform: none;\n    }\n  }\n}\n"}],"categories":["feedback"],"meta":{"tier":"free","license":"MIT","family":"feedback","docs":"/docs/components/progress"}}