{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hold-button-demo",
  "type": "registry:example",
  "title": "Hold Button Demo",
  "description": "A destructive action that only goes through if you keep holding.",
  "registryDependencies": [
    "@loomui/hold-button"
  ],
  "files": [
    {
      "path": "registry/example/hold-button-demo.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { HoldButton } from \"@/registry/loomui/hold-button\"\n\nconst CONFIRM_MS = 1500\n/** Exits run shorter than entrances. */\nconst EXIT_MS = 150\nconst ENTER_MS = 200\n/** Enough that the two are never both half there, smearing together. */\nconst ENTER_DELAY = 120\n\n/** One grid cell for both halves, so the swap is opacity and nothing else. */\nconst SWAP =\n  \"col-start-1 row-start-1 will-change-[opacity] transition-opacity ease-[var(--ease-out-quart)] motion-reduce:transition-none\"\n\nexport default function HoldButtonDemo() {\n  const [done, setDone] = React.useState(false)\n  const button = React.useRef<HTMLButtonElement>(null)\n  const hadFocus = React.useRef(false)\n  const timers = React.useRef<ReturnType<typeof setTimeout>[]>([])\n\n  React.useEffect(\n    () => () => timers.current.forEach((timer) => clearTimeout(timer)),\n    []\n  )\n\n  const handleHold = () => {\n    // Held with Space? Hand focus back, or the swap drops them on the body.\n    hadFocus.current = document.activeElement === button.current\n    setDone(true)\n\n    timers.current = [\n      setTimeout(() => setDone(false), CONFIRM_MS),\n      setTimeout(\n        () => {\n          // Focusing scrolls by default, which yanks the page mid swap.\n          if (hadFocus.current) button.current?.focus({ preventScroll: true })\n        },\n        CONFIRM_MS + ENTER_DELAY + ENTER_MS\n      ),\n    ]\n  }\n\n  return (\n    <div className=\"grid place-items-center\">\n      <span\n        className={`${SWAP} ${done ? \"opacity-0\" : \"opacity-100 delay-[120ms]\"}`}\n        style={{ transitionDuration: `${done ? EXIT_MS : ENTER_MS}ms` }}\n      >\n        <HoldButton\n          ref={button}\n          duration={1100}\n          onHold={handleHold}\n          color=\"color-mix(in oklch, var(--destructive) 22%, transparent)\"\n          className=\"text-destructive px-4 py-2 text-sm font-medium\"\n          tabIndex={done ? -1 : undefined}\n          inert={done}\n        >\n          Hold to delete\n        </HoldButton>\n      </span>\n\n      <span\n        aria-hidden=\"true\"\n        className={`${SWAP} text-muted-foreground pointer-events-none flex items-center gap-2 text-sm ${\n          done ? \"opacity-100 delay-[120ms]\" : \"opacity-0\"\n        }`}\n        style={{ transitionDuration: `${done ? ENTER_MS : EXIT_MS}ms` }}\n      >\n        <svg\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2.5\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          className=\"text-accent size-4\"\n        >\n          <path d=\"M20 6 9 17l-5-5\" />\n        </svg>\n        Gone. No takebacks\n      </span>\n\n      {/* The visible half is decoration. This is what is announced. */}\n      <span role=\"status\" className=\"sr-only\">\n        {done ? \"Deleted\" : \"\"}\n      </span>\n    </div>\n  )\n}\n",
      "type": "registry:example"
    }
  ]
}