|
| 1 | +import { useRef, useState } from 'react' |
| 2 | +import { writeTextToClipboard } from '@/utils/clipboard' |
| 3 | +import { noop } from './noop' |
| 4 | +import { useStableHandler } from './use-stable-handler-only-when-you-know-what-you-are-doing-or-you-will-be-fired' |
| 5 | +import { useCallback } from './use-typescript-happy-callback' |
| 6 | +import 'client-only' |
| 7 | + |
| 8 | +type UseClipboardOption = { |
| 9 | + timeout?: number |
| 10 | + usePromptAsFallback?: boolean |
| 11 | + promptFallbackText?: string |
| 12 | + onCopyError?: (error: Error) => void |
| 13 | +} |
| 14 | + |
| 15 | +/** @see https://foxact.skk.moe/use-clipboard */ |
| 16 | +export function useClipboard({ |
| 17 | + timeout = 1000, |
| 18 | + usePromptAsFallback = false, |
| 19 | + promptFallbackText = 'Failed to copy to clipboard automatically, please manually copy the text below.', |
| 20 | + onCopyError, |
| 21 | +}: UseClipboardOption = {}) { |
| 22 | + const [error, setError] = useState<Error | null>(null) |
| 23 | + const [copied, setCopied] = useState(false) |
| 24 | + const copyTimeoutRef = useRef<number | null>(null) |
| 25 | + |
| 26 | + const stablizedOnCopyError = useStableHandler<[e: Error], void>(onCopyError || noop) |
| 27 | + |
| 28 | + const handleCopyResult = useCallback((isCopied: boolean) => { |
| 29 | + if (copyTimeoutRef.current) { |
| 30 | + clearTimeout(copyTimeoutRef.current) |
| 31 | + } |
| 32 | + if (isCopied) { |
| 33 | + copyTimeoutRef.current = window.setTimeout(() => setCopied(false), timeout) |
| 34 | + } |
| 35 | + setCopied(isCopied) |
| 36 | + }, [timeout]) |
| 37 | + |
| 38 | + const handleCopyError = useCallback((e: Error) => { |
| 39 | + setError(e) |
| 40 | + stablizedOnCopyError(e) |
| 41 | + }, [stablizedOnCopyError]) |
| 42 | + |
| 43 | + const copy = useCallback(async (valueToCopy: string) => { |
| 44 | + try { |
| 45 | + await writeTextToClipboard(valueToCopy) |
| 46 | + } |
| 47 | + catch (e) { |
| 48 | + if (usePromptAsFallback) { |
| 49 | + try { |
| 50 | + // eslint-disable-next-line no-alert -- prompt as fallback in case of copy error |
| 51 | + window.prompt(promptFallbackText, valueToCopy) |
| 52 | + } |
| 53 | + catch (e2) { |
| 54 | + handleCopyError(e2 as Error) |
| 55 | + } |
| 56 | + } |
| 57 | + else { |
| 58 | + handleCopyError(e as Error) |
| 59 | + } |
| 60 | + } |
| 61 | + }, [handleCopyResult, promptFallbackText, handleCopyError, usePromptAsFallback]) |
| 62 | + |
| 63 | + const reset = useCallback(() => { |
| 64 | + setCopied(false) |
| 65 | + setError(null) |
| 66 | + if (copyTimeoutRef.current) { |
| 67 | + clearTimeout(copyTimeoutRef.current) |
| 68 | + } |
| 69 | + }, []) |
| 70 | + |
| 71 | + return { copy, reset, error, copied } |
| 72 | +} |
0 commit comments