{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hold-to-delete-button",
  "title": "Hold to Delete Button",
  "author": "henriquegpb <https://github.com/henriquegpb>",
  "description": "A delete button that only fires when held: an orange bar fills linearly from left to right as a matching glow builds behind it, snapping back if you let go early and landing on a check with a pulse once it completes.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/microkit/hold-to-delete-button.tsx",
      "content": "\"use client\";\n\nimport { Check, Trash2 } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport type { KeyboardEvent, PointerEvent } from \"react\";\n\ntype DeleteState = \"idle\" | \"holding\" | \"deleted\";\n\nconst HOLD_MS = 1600;\nconst RESET_MS = 1800;\n\nconst pulseKeyframes = `\n@keyframes hold-delete-pulse {\n  from { outline: 1px solid rgba(249, 115, 22, .9); outline-offset: 0; }\n  to { outline: 1px solid rgba(249, 115, 22, 0); outline-offset: 12px; }\n}`;\n\nfunction ButtonContent({ deleted }: { deleted: boolean }) {\n  return (\n    <span className=\"inline-flex items-center gap-[10px]\">\n      <span className=\"grid shrink-0\" aria-hidden=\"true\">\n        <Trash2\n          className=\"[grid-area:1/1] [transition:opacity_.24s_ease,transform_.32s_cubic-bezier(.16,1,.3,1)] data-[active=false]:opacity-0 data-[active=false]:[transform:scale(.5)] motion-reduce:transition-none\"\n          data-active={!deleted}\n          size={20}\n          strokeWidth={2}\n        />\n        <Check\n          className=\"[grid-area:1/1] [transition:opacity_.24s_ease,transform_.32s_cubic-bezier(.16,1,.3,1)] data-[active=false]:opacity-0 data-[active=false]:[transform:scale(.5)] motion-reduce:transition-none\"\n          data-active={deleted}\n          size={20}\n          strokeWidth={2.4}\n        />\n      </span>\n      <span className=\"grid\">\n        <span\n          className=\"justify-self-start whitespace-nowrap [grid-area:1/1] [transition:opacity_.24s_ease,transform_.32s_cubic-bezier(.16,1,.3,1),filter_.24s_ease,visibility_.24s] data-[active=false]:invisible data-[active=false]:opacity-0 data-[active=false]:[transform:translateY(6px)] data-[active=false]:[filter:blur(2px)] motion-reduce:transition-none\"\n          data-active={!deleted}\n        >\n          Hold to delete\n        </span>\n        <span\n          className=\"justify-self-start whitespace-nowrap [grid-area:1/1] [transition:opacity_.24s_ease,transform_.32s_cubic-bezier(.16,1,.3,1),filter_.24s_ease,visibility_.24s] data-[active=false]:invisible data-[active=false]:opacity-0 data-[active=false]:[transform:translateY(6px)] data-[active=false]:[filter:blur(2px)] motion-reduce:transition-none\"\n          data-active={deleted}\n        >\n          Deleted\n        </span>\n      </span>\n    </span>\n  );\n}\n\nexport function HoldToDeleteButton() {\n  const [state, setState] = useState<DeleteState>(\"idle\");\n\n  useEffect(() => {\n    if (state === \"idle\") return;\n    const timeout = window.setTimeout(\n      () => setState(state === \"holding\" ? \"deleted\" : \"idle\"),\n      state === \"holding\" ? HOLD_MS : RESET_MS,\n    );\n    return () => window.clearTimeout(timeout);\n  }, [state]);\n\n  const start = () => setState((current) => (current === \"idle\" ? \"holding\" : current));\n  const cancel = () => setState((current) => (current === \"holding\" ? \"idle\" : current));\n\n  const handlePointerDown = (event: PointerEvent<HTMLButtonElement>) => {\n    if (event.button === 0) start();\n  };\n\n  const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n    if (event.key !== \" \" && event.key !== \"Enter\") return;\n    event.preventDefault();\n    if (!event.repeat) start();\n  };\n\n  const handleKeyUp = (event: KeyboardEvent<HTMLButtonElement>) => {\n    if (event.key === \" \" || event.key === \"Enter\") cancel();\n  };\n\n  return (\n    <>\n      <style>{pulseKeyframes}</style>\n      <button\n        type=\"button\"\n        className=\"group relative inline-flex h-[52px] cursor-pointer appearance-none items-center justify-center overflow-hidden rounded-full border-0 bg-[light-dark(#ffffff,#121418)] px-[24px] [font-family:inherit] text-[17px] font-medium leading-none tracking-[-.01em] text-[light-dark(#14181e,#f2f4f6)] [box-shadow:inset_0_0_0_1px_light-dark(#dfe3ea,#2b2f36),0_10px_24px_light-dark(rgba(246,247,249,.35),rgba(0,0,0,.35)),0_8px_28px_rgba(249,115,22,0)] data-[state=holding]:[box-shadow:inset_0_0_0_1px_light-dark(#dfe3ea,#2b2f36),0_10px_24px_light-dark(rgba(246,247,249,.35),rgba(0,0,0,.35)),0_8px_28px_rgba(249,115,22,.55)] data-[state=deleted]:[box-shadow:inset_0_0_0_1px_light-dark(#dfe3ea,#2b2f36),0_10px_24px_light-dark(rgba(246,247,249,.35),rgba(0,0,0,.35)),0_8px_28px_rgba(249,115,22,.55)] select-none [-webkit-touch-callout:none] touch-manipulation [transition:transform_.25s_cubic-bezier(.16,1,.3,1),background-color_.2s_ease,box-shadow_.5s_cubic-bezier(.16,1,.3,1)] data-[state=holding]:[transition:transform_.25s_cubic-bezier(.16,1,.3,1),background-color_.2s_ease,box-shadow_1.6s_linear] hover:data-[state=idle]:bg-[light-dark(#f0f2f5,#1b1e23)] motion-safe:data-[state=holding]:[transform:scale(.97)] data-[state=deleted]:cursor-default motion-safe:data-[state=deleted]:[animation:hold-delete-pulse_.8s_cubic-bezier(.16,1,.3,1)] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[#f97316] motion-reduce:data-[state=idle]:[transition:none] motion-reduce:data-[state=deleted]:[transition:none] motion-reduce:data-[state=holding]:[transition:box-shadow_1.6s_linear]\"\n        data-state={state}\n        onPointerDown={handlePointerDown}\n        onPointerUp={cancel}\n        onPointerLeave={cancel}\n        onPointerCancel={cancel}\n        onKeyDown={handleKeyDown}\n        onKeyUp={handleKeyUp}\n        onBlur={cancel}\n        onContextMenu={(event) => event.preventDefault()}\n      >\n        <ButtonContent deleted={state === \"deleted\"} />\n        <span\n          className=\"pointer-events-none absolute inset-0 flex items-center justify-center bg-[#f97316] text-white [clip-path:inset(0_100%_0_0)] [transition:clip-path_.5s_cubic-bezier(.16,1,.3,1)] group-data-[state=holding]:[clip-path:inset(0)] group-data-[state=holding]:[transition:clip-path_1.6s_linear] group-data-[state=deleted]:[clip-path:inset(0)] motion-reduce:group-data-[state=idle]:[transition:none] motion-reduce:group-data-[state=deleted]:[transition:none]\"\n          aria-hidden=\"true\"\n        >\n          <ButtonContent deleted={state === \"deleted\"} />\n        </span>\n      </button>\n    </>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/microkit/hold-to-delete-button.tsx"
    }
  ],
  "docs": "Live preview and the CSS, JavaScript and Tailwind variants: https://microkit.co/components/hold-to-delete-button",
  "categories": [
    "button",
    "click"
  ],
  "type": "registry:component"
}