{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress",
  "type": "registry:ui",
  "title": "Progress",
  "description": "Show how much of a known task is complete.",
  "categories": [
    "feedback"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/motion-tokens.json",
    "https://irisui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/registry/components/progress/progress.tsx",
      "content": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useIsPresent,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n  type HTMLMotionProps,\n  type TargetAndTransition,\n  type Transition,\n} from \"motion/react\";\nimport { Check } from \"lucide-react\";\nimport { motionTokens } from \"../lib/motion-tokens\";\nimport { cn } from \"../lib/utils\";\n\nexport interface ProgressProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  value?: number;\n  max?: number;\n  label?: string;\n  showValue?: boolean;\n}\n\nconst exitFast: Transition = { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] };\nconst textIn: TargetAndTransition = { opacity: 0, y: \"0.3em\", filter: `blur(${motionTokens.blur.soft}px)` };\nconst textOut: TargetAndTransition = {\n  opacity: 0,\n  y: \"-0.3em\",\n  filter: `blur(${motionTokens.blur.subtle}px)`,\n  transition: exitFast,\n};\nconst iconIn: TargetAndTransition = { opacity: 0, scale: 0.6, filter: `blur(${motionTokens.blur.subtle}px)` };\nconst shown: TargetAndTransition = { opacity: 1, y: \"0em\", scale: 1, filter: \"blur(0px)\" };\nconst fadeOut: TargetAndTransition = { opacity: 0, transition: { duration: motionTokens.duration.instant } };\n\n/** Outgoing copies are hidden from assistive tech while they fade. */\nfunction Swap(props: HTMLMotionProps<\"span\">) {\n  const present = useIsPresent();\n  return <motion.span {...props} aria-hidden={present ? props[\"aria-hidden\"] : true} />;\n}\n\nexport function Progress({ value = 0, max = 100, label, showValue = false, className, ...props }: ProgressProps) {\n  const reduce = useReducedMotion();\n  const safeMax = max > 0 ? max : 100;\n  const safeValue = Math.min(Math.max(value, 0), safeMax);\n  const percentage = Math.round((safeValue / safeMax) * 100);\n  const complete = percentage >= 100;\n  // One spring drives both the fill and the counted label, so the number always matches the bar.\n  const progress = useMotionValue(percentage);\n  // The fill slides in from the left instead of scaling, so its rounded end keeps its shape at every value.\n  const x = useTransform(progress, (latest) => `${Math.min(Math.max(latest, 0), 100) - 100}%`);\n  const counted = useTransform(progress, (latest) => `${Math.round(Math.min(Math.max(latest, 0), 100))}%`);\n  useEffect(() => {\n    if (reduce) {\n      progress.jump(percentage);\n      return;\n    }\n    const controls = animate(progress, percentage, motionTokens.spring.smooth);\n    return () => controls.stop();\n  }, [percentage, progress, reduce]);\n  const enter: Transition = reduce\n    ? { duration: motionTokens.duration.instant }\n    : { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] };\n  return (\n    <div\n      {...props}\n      className={cn(\"w-full text-(length:--text-sm) text-ink-2\", className)}\n      data-complete={complete ? \"\" : undefined}\n      role=\"progressbar\"\n      aria-label={label ?? \"Progress\"}\n      aria-valuemin={0}\n      aria-valuemax={safeMax}\n      aria-valuenow={safeValue}\n      aria-valuetext={`${percentage}%`}\n    >\n      {label || showValue ? (\n        <div className=\"mb-2 flex justify-between gap-3 leading-body\">\n          {label ? (\n            <span className=\"relative min-w-0\">\n              <AnimatePresence mode=\"popLayout\" initial={false}>\n                <Swap\n                  key={label}\n                  className=\"block\"\n                  initial={reduce ? { opacity: 0 } : textIn}\n                  animate={shown}\n                  exit={reduce ? fadeOut : textOut}\n                  transition={enter}\n                >\n                  {label}\n                </Swap>\n              </AnimatePresence>\n            </span>\n          ) : (\n            <span />\n          )}\n          {showValue ? (\n            <span className=\"inline-flex flex-none items-center gap-1 text-ink-3 tabular-nums\">\n              {/* A hidden \"100%\" row in the count reserves the widest value, so the check never shifts as the number gains a digit. */}\n              {/* Completion lands as the fill arrives: a check settles in beside the final count. */}\n              <AnimatePresence initial={false}>\n                {complete && (\n                  <motion.span\n                    key=\"done\"\n                    className=\"grid place-items-center text-green\"\n                    initial={reduce ? { opacity: 0 } : iconIn}\n                    animate={shown}\n                    exit={reduce ? fadeOut : { ...iconIn, transition: exitFast }}\n                    transition={reduce ? enter : { ...motionTokens.spring.snappy, delay: 0.24 }}\n                  >\n                    <Check size={14} strokeWidth={1.75} aria-hidden=\"true\" />\n                  </motion.span>\n                )}\n              </AnimatePresence>\n              <motion.span className=\"inline-grid justify-items-end before:invisible before:h-0 before:overflow-hidden before:content-['100%']\">\n                {counted}\n              </motion.span>\n            </span>\n          ) : null}\n        </div>\n      ) : null}\n      <div className=\"h-[7px] overflow-hidden rounded-[99px] border border-line bg-hover\">\n        <motion.span\n          className={cn(\n            \"block size-full rounded-[inherit] transition-[background-color] duration-standard ease-standard motion-reduce:transition-none\",\n            complete ? \"bg-green delay-200\" : \"bg-accent\",\n          )}\n          style={{ x }}\n        />\n      </div>\n    </div>\n  );\n}\n\nexport default Progress;\n",
      "type": "registry:component",
      "target": "@components/iris/progress/progress.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/progress",
  "meta": {
    "kind": "component",
    "group": "Feedback",
    "export": "Progress",
    "tags": [
      "status",
      "loading"
    ],
    "docs": "https://irisui.dev/components/progress"
  }
}
