{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hold-button",
  "title": "Hold Button",
  "description": "A destructive action that needs a press and hold, with a fill that shows progress.",
  "registryDependencies": [
    "https://spotcheckui.com/r/spotcheck-base.json",
    "https://spotcheckui.com/r/button.json"
  ],
  "files": [
    {
      "path": "registry/spotcheck/components/spotcheck/hold-button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonClass } from \"@/registry/spotcheck/components/spotcheck/button\"\n\nconst DURATION_MS = 1200\nconst HOLD_KEYS = [\" \", \"Enter\"]\n\ntype HoldButtonProps = Omit<React.ComponentProps<\"button\">, \"onClick\"> & {\n  /** How long to hold, in milliseconds. */\n  duration?: number\n  /** Runs once the hold completes. */\n  onConfirm?: () => void\n  /** Replaces the label after the hold completes. Pressing again resets the button. */\n  confirmedLabel?: string\n}\n\nexport function HoldButton({\n  duration = DURATION_MS,\n  onConfirm,\n  confirmedLabel = \"Done · press to reset\",\n  className,\n  children,\n  ...props\n}: HoldButtonProps) {\n  const [confirmed, setConfirmed] = React.useState(false)\n  const fill = React.useRef<HTMLSpanElement>(null)\n  const frame = React.useRef(0)\n  const heldKey = React.useRef<string | null>(null)\n\n  const paint = (progress: number) => {\n    if (fill.current) fill.current.style.transform = `scaleX(${progress})`\n  }\n\n  const start = () => {\n    if (confirmed) return\n    const startedAt = performance.now()\n    const step = (now: number) => {\n      const progress = Math.min(1, (now - startedAt) / duration)\n      paint(progress)\n      if (progress < 1) {\n        frame.current = requestAnimationFrame(step)\n      } else {\n        setConfirmed(true)\n        onConfirm?.()\n      }\n    }\n    frame.current = requestAnimationFrame(step)\n  }\n\n  const stop = () => {\n    cancelAnimationFrame(frame.current)\n    heldKey.current = null\n    if (!confirmed) paint(0)\n  }\n\n  const reset = () => {\n    setConfirmed(false)\n    paint(0)\n  }\n\n  React.useEffect(() => () => cancelAnimationFrame(frame.current), [])\n\n  return (\n    <button\n      type=\"button\"\n      className={buttonClass(\"outline\", cn(\"overflow-hidden\", confirmed && \"text-on-accent\", className))}\n      onPointerDown={start}\n      onPointerUp={stop}\n      onPointerLeave={stop}\n      onPointerCancel={stop}\n      onBlur={stop}\n      onClick={() => confirmed && heldKey.current === null && reset()}\n      onKeyDown={(event) => {\n        if (!HOLD_KEYS.includes(event.key)) return\n        event.preventDefault()\n        if (event.repeat) return\n        if (confirmed) return reset()\n        heldKey.current = event.key\n        start()\n      }}\n      onKeyUp={(event) => {\n        if (event.key === heldKey.current) stop()\n      }}\n      {...props}\n    >\n      <span\n        ref={fill}\n        aria-hidden\n        data-part=\"fill\"\n        className=\"bg-bad absolute inset-0 origin-left scale-x-0\"\n      />\n      <span className=\"relative\">{confirmed ? confirmedLabel : children}</span>\n      <span role=\"status\" className=\"sr-only\">\n        {confirmed ? \"Confirmed\" : \"\"}\n      </span>\n    </button>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "core"
  ],
  "type": "registry:component"
}