{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hold-button",
  "title": "Hold Button",
  "description": "A confirmation button that completes after a deliberate pointer hold and remains accessible from a keyboard.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/hold-button/hold-button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn } from \"@/lib/utils\"\n\ntype HoldState = \"idle\" | \"holding\" | \"complete\"\n\nexport interface HoldButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  onComplete: () => void\n  duration?: number\n  completeLabel?: React.ReactNode\n}\n\nexport const HoldButton = React.forwardRef<HTMLButtonElement, HoldButtonProps>(\n  function HoldButton(\n    {\n      children = \"Hold to confirm\",\n      completeLabel = \"Done\",\n      onComplete,\n      duration = 900,\n      disabled,\n      className,\n      onClick,\n      onLostPointerCapture,\n      onPointerCancel,\n      onPointerDown,\n      onPointerUp,\n      type = \"button\",\n      ...props\n    },\n    forwardedRef\n  ) {\n    const [progress, setProgress] = React.useState(0)\n    const [state, setState] = React.useState<HoldState>(\"idle\")\n    const stateRef = React.useRef<HoldState>(\"idle\")\n    const frameRef = React.useRef<number | null>(null)\n    const resetTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n      null\n    )\n    const onCompleteRef = React.useRef(onComplete)\n\n    React.useEffect(() => {\n      onCompleteRef.current = onComplete\n    }, [onComplete])\n\n    const clearFrame = React.useCallback(() => {\n      if (frameRef.current !== null) {\n        cancelAnimationFrame(frameRef.current)\n        frameRef.current = null\n      }\n    }, [])\n\n    const finish = React.useCallback(() => {\n      clearFrame()\n      stateRef.current = \"complete\"\n      setState(\"complete\")\n      setProgress(1)\n      onCompleteRef.current()\n\n      resetTimerRef.current = setTimeout(() => {\n        stateRef.current = \"idle\"\n        setState(\"idle\")\n        setProgress(0)\n      }, 1200)\n    }, [clearFrame])\n\n    const cancel = React.useCallback(() => {\n      if (stateRef.current !== \"holding\") return\n      clearFrame()\n      stateRef.current = \"idle\"\n      setState(\"idle\")\n      setProgress(0)\n    }, [clearFrame])\n\n    const begin = React.useCallback(() => {\n      if (disabled || stateRef.current !== \"idle\") return\n\n      const safeDuration = Math.max(duration, 500)\n      const startedAt = performance.now()\n      stateRef.current = \"holding\"\n      setState(\"holding\")\n\n      const tick = (now: number) => {\n        const nextProgress = Math.min((now - startedAt) / safeDuration, 1)\n        setProgress(nextProgress)\n\n        if (nextProgress >= 1) {\n          finish()\n          return\n        }\n\n        frameRef.current = requestAnimationFrame(tick)\n      }\n\n      frameRef.current = requestAnimationFrame(tick)\n    }, [disabled, duration, finish])\n\n    React.useEffect(() => {\n      return () => {\n        clearFrame()\n        if (resetTimerRef.current) clearTimeout(resetTimerRef.current)\n      }\n    }, [clearFrame])\n\n    return (\n      <button\n        {...props}\n        data-slot=\"hold-button\"\n        data-state={state}\n        ref={forwardedRef}\n        type={type}\n        disabled={disabled}\n        onPointerDown={(event) => {\n          onPointerDown?.(event)\n          if (event.defaultPrevented) return\n          if (event.button !== 0) return\n          event.currentTarget.setPointerCapture(event.pointerId)\n          begin()\n        }}\n        onPointerUp={(event) => {\n          onPointerUp?.(event)\n          if (!event.defaultPrevented) cancel()\n        }}\n        onPointerCancel={(event) => {\n          onPointerCancel?.(event)\n          if (!event.defaultPrevented) cancel()\n        }}\n        onLostPointerCapture={(event) => {\n          onLostPointerCapture?.(event)\n          if (!event.defaultPrevented) cancel()\n        }}\n        onClick={(event) => {\n          onClick?.(event)\n          if (event.defaultPrevented) return\n          if (event.detail === 0 && stateRef.current === \"idle\") finish()\n        }}\n        className={cn(\n          \"border-destructive/30 bg-destructive text-destructive-foreground focus-visible:ring-ring/35 relative isolate inline-flex min-h-11 min-w-44 cursor-default items-center justify-center overflow-hidden rounded-[var(--radius)] border px-5 py-2.5 text-sm font-semibold shadow-sm outline-none select-none focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50 data-[state=holding]:scale-[0.98] motion-safe:transition-transform motion-safe:duration-150\",\n          className\n        )}\n      >\n        <span\n          aria-hidden=\"true\"\n          className=\"bg-foreground/25 absolute inset-0 -z-10 origin-left\"\n          style={{ transform: `scaleX(${progress})` }}\n        />\n        <span>{state === \"complete\" ? completeLabel : children}</span>\n        <span className=\"sr-only\" aria-live=\"polite\">\n          {state === \"holding\"\n            ? `Confirmation ${Math.round(progress * 100)} percent complete`\n            : state === \"complete\"\n              ? \"Action complete\"\n              : \"\"}\n        </span>\n      </button>\n    )\n  }\n)\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "button",
    "confirmation",
    "feedback"
  ],
  "type": "registry:ui"
}