| "use client"; | |
| import { useState, useCallback, useRef } from "react"; | |
| /** | |
| * Fallback copy using legacy execCommand (works on HTTP) | |
| */ | |
| function fallbackCopy(text) { | |
| const textarea = document.createElement("textarea"); | |
| textarea.value = text; | |
| textarea.style.position = "fixed"; | |
| textarea.style.left = "-9999px"; | |
| textarea.style.top = "-9999px"; | |
| textarea.style.opacity = "0"; | |
| document.body.appendChild(textarea); | |
| textarea.focus(); | |
| textarea.select(); | |
| try { | |
| document.execCommand("copy"); | |
| } catch { | |
| // ignore | |
| } | |
| document.body.removeChild(textarea); | |
| } | |
| /** | |
| * Hook for copy to clipboard with feedback | |
| * @param {number} resetDelay - Time in ms before resetting copied state (default: 2000) | |
| * @returns {{ copied: string|null, copy: (text: string, id?: string) => void }} | |
| */ | |
| export function useCopyToClipboard(resetDelay = 2000) { | |
| const [copied, setCopied] = useState(null); | |
| const timeoutRef = useRef(null); | |
| const copy = useCallback( | |
| async (text, id = "default") => { | |
| try { | |
| if (navigator.clipboard && window.isSecureContext) { | |
| await navigator.clipboard.writeText(text); | |
| } else { | |
| fallbackCopy(text); | |
| } | |
| } catch { | |
| fallbackCopy(text); | |
| } | |
| setCopied(id); | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| timeoutRef.current = setTimeout(() => { | |
| setCopied(null); | |
| }, resetDelay); | |
| }, | |
| [resetDelay] | |
| ); | |
| return { copied, copy }; | |
| } | |