Aur UI

Use Copy To Clipboard

Copy text with a self-resetting copied flag — pairs with iconSwapVariants for the Copy ↔ Check affordance.

bash
pnpm add @aur-ui/ui
tsx
import { useCopyToClipboard } from "@aur-ui/ui/hooks/use-copy-to-clipboard";

function CopyValue({ value }: { value: string }) {
  const { copied, copy } = useCopyToClipboard();

  return (
    <button type="button" onClick={() => void copy(value)}>
      {copied ? <CheckIcon /> : <CopyIcon />}
    </button>
  );
}

copy resolves to whether the write succeeded (missing clipboard API or a denied permission returns false, it never throws). The copied flag resets after 1.5s by default — pass useCopyToClipboard(ms) to change it; rapid re-copies restart the timer, and unmount clears it.

hooks/use-copy-to-clipboard.ts
"use client";

import { useCallback, useEffect, useRef, useState } from "react";

/**
 * Copy-to-clipboard with a self-resetting `copied` flag — pairs with
 * `iconSwapVariants` for the Copy ↔ Check affordance.
 *
 * `copy` resolves to whether the write succeeded (clipboard API missing or
 * permission denied → `false`, never throws). The flag resets after
 * `resetDelay` ms; rapid re-copies restart the timer, and unmount clears it.
 */
export function useCopyToClipboard(resetDelay = 1500): {
  copied: boolean;
  copy: (text: string) => Promise<boolean>;
} {
  const [copied, setCopied] = useState(false);
  const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  useEffect(() => () => clearTimeout(timerRef.current), []);

  const copy = useCallback(
    async (text: string) => {
      if (!navigator.clipboard) return false;
      try {
        await navigator.clipboard.writeText(text);
      } catch {
        return false;
      }
      setCopied(true);
      clearTimeout(timerRef.current);
      timerRef.current = setTimeout(() => setCopied(false), resetDelay);
      return true;
    },
    [resetDelay],
  );

  return { copied, copy };
}