button-copy

PreviousNext

A ButtonCopy component for SmoothUI.

Docs
smoothuiui

Preview

Loading preview…
index.tsx
"use client";

import { Check, Copy, LoaderCircle } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { type ReactNode, useCallback, useState } from "react";

export type ButtonCopyProps = {
  onCopy?: () => Promise<void> | void;
  idleIcon?: ReactNode;
  loadingIcon?: ReactNode;
  successIcon?: ReactNode;
  className?: string;
  duration?: number;
  loadingDuration?: number;
  disabled?: boolean;
};

const defaultIcons = {
  idle: <Copy size={16} />,
  loading: <LoaderCircle className="animate-spin" size={16} />,
  success: <Check size={16} />,
};

export default function ButtonCopy({
  onCopy,
  idleIcon = defaultIcons.idle,
  loadingIcon = defaultIcons.loading,
  successIcon = defaultIcons.success,
  className = "",
  duration = 2000,
  loadingDuration = 1000,
  disabled = false,
}: ButtonCopyProps) {
  const [buttonState, setButtonState] = useState<
    "idle" | "loading" | "success"
  >("idle");

  const handleClick = useCallback(async () => {
    setButtonState("loading");
    if (onCopy) {
      await onCopy();
    }
    setTimeout(() => {
      setButtonState("success");
    }, loadingDuration);
    setTimeout(() => {
      setButtonState("idle");
    }, loadingDuration + duration);
  }, [onCopy, loadingDuration, duration]);

  const icons = {
    idle: idleIcon,
    loading: loadingIcon,
    success: successIcon,
  };

  return (
    <div className="flex justify-center">
      <button
        aria-label={buttonState === "loading" ? "Copying..." : "Copy"}
        className={`relative w-auto cursor-pointer overflow-hidden rounded-full border bg-background p-3 disabled:opacity-50 ${className}`}
        disabled={buttonState !== "idle" || disabled}
        onClick={handleClick}
        type="button"
      >
        <AnimatePresence initial={false} mode="popLayout">
          <motion.span
            animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
            className="flex w-full items-center justify-center"
            exit={{ opacity: 0, y: 25, filter: "blur(10px)" }}
            initial={{ opacity: 0, y: -25, filter: "blur(10px)" }}
            key={buttonState}
            transition={{ type: "spring", duration: 0.3, bounce: 0 }}
          >
            {icons[buttonState]}
          </motion.span>
        </AnimatePresence>
      </button>
    </div>
  );
}

Installation

npx shadcn@latest add @smoothui/button-copy

Usage

import { ButtonCopy } from "@/components/ui/button-copy"
<ButtonCopy />