{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "p-button-40",
  "description": "Download button with progress and cancel action",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@coss/button",
    "@coss/group",
    "@coss/spinner",
    "@coss/toast",
    "@coss/tooltip"
  ],
  "files": [
    {
      "path": "registry/default/particles/p-button-40.tsx",
      "content": "\"use client\";\n\nimport { DownloadIcon, XIcon } from \"lucide-react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport { Group, GroupSeparator, GroupText } from \"@/registry/default/ui/group\";\nimport { Spinner } from \"@/registry/default/ui/spinner\";\nimport { toastManager } from \"@/registry/default/ui/toast\";\nimport {\n  Tooltip,\n  TooltipPopup,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/registry/default/ui/tooltip\";\n\nexport default function Particle() {\n  const [isDownloading, setIsDownloading] = useState(false);\n  const [progress, setProgress] = useState(0);\n  const abortControllerRef = useRef<AbortController | null>(null);\n  const infoToastIdRef = useRef<string | null>(null);\n\n  useEffect(() => {\n    if (!isDownloading) return;\n\n    const interval = setInterval(() => {\n      setProgress((prev) =>\n        Math.min(99, prev + Math.round(Math.random() * 8 + 2)),\n      );\n    }, 300);\n\n    return () => clearInterval(interval);\n  }, [isDownloading]);\n\n  async function handleDownload() {\n    if (isDownloading) return;\n\n    setIsDownloading(true);\n    setProgress(0);\n    abortControllerRef.current = new AbortController();\n\n    infoToastIdRef.current = toastManager.add({\n      description: \"Your download will begin once ready.\",\n      title: \"Generating report…\",\n      type: \"info\",\n    });\n\n    try {\n      await new Promise<string>((resolve, reject) => {\n        const shouldSucceed = Math.random() > 0.2;\n        const timeoutId = setTimeout(() => {\n          if (shouldSucceed) {\n            resolve(\"Download complete\");\n          } else {\n            reject(new Error(\"Download failed\"));\n          }\n        }, 4000);\n\n        abortControllerRef.current?.signal.addEventListener(\"abort\", () => {\n          clearTimeout(timeoutId);\n          reject(new DOMException(\"Cancelled\", \"AbortError\"));\n        });\n      });\n    } catch (err) {\n      // Close info toast before showing error\n      if (infoToastIdRef.current) {\n        toastManager.close(infoToastIdRef.current);\n        infoToastIdRef.current = null;\n      }\n\n      if (err instanceof DOMException && err.name === \"AbortError\") {\n        // Cancelled\n        toastManager.add({\n          description: \"Report generation was cancelled.\",\n          title: \"Cancelled\",\n          type: \"error\",\n        });\n      } else {\n        // Other errors\n        toastManager.add({\n          description: \"Please try again later.\",\n          title: \"Failed to generate report\",\n          type: \"error\",\n        });\n      }\n    } finally {\n      setIsDownloading(false);\n      setProgress(0);\n      abortControllerRef.current = null;\n      infoToastIdRef.current = null;\n    }\n  }\n\n  function handleCancel() {\n    abortControllerRef.current?.abort();\n  }\n\n  return (\n    <TooltipProvider delay={0}>\n      {isDownloading ? (\n        <Group>\n          <GroupText\n            aria-live=\"polite\"\n            className=\"cursor-default gap-2\"\n            role=\"status\"\n          >\n            <Spinner />\n            <span\n              aria-hidden=\"true\"\n              className=\"font-medium text-foreground tabular-nums\"\n            >\n              {progress.toString().padStart(2, \"\\u2007\")}%\n            </span>\n            <span className=\"sr-only\">\n              Generating report, {progress}% complete\n            </span>\n          </GroupText>\n          <GroupSeparator />\n          <Tooltip>\n            <TooltipTrigger\n              render={\n                <Button\n                  aria-label=\"Cancel download\"\n                  onClick={handleCancel}\n                  size=\"icon\"\n                  variant=\"outline\"\n                />\n              }\n            >\n              <XIcon aria-hidden=\"true\" />\n            </TooltipTrigger>\n            <TooltipPopup>Cancel</TooltipPopup>\n          </Tooltip>\n        </Group>\n      ) : (\n        <Button onClick={handleDownload} variant=\"outline\">\n          <DownloadIcon aria-hidden=\"true\" />\n          Download\n        </Button>\n      )}\n    </TooltipProvider>\n  );\n}\n",
      "type": "registry:block"
    }
  ],
  "categories": [
    "button",
    "group",
    "tooltip",
    "toast"
  ],
  "type": "registry:block"
}