Button Copy
Button Copy é um componente de botão para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
SmoothUI
MIT
buttons
O que é
Button Copy é um componente de botão para React e Tailwind CSS, da biblioteca SmoothUI, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i motion lucide-react
Licença
Este componente vem de SmoothUI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Button Copy
"use client";
import { Check, Copy, LoaderCircle } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { type ReactNode, useCallback, useState } from "react";
export interface ButtonCopyProps {
className?: string;
disabled?: boolean;
duration?: number;
idleIcon?: ReactNode;
loadingDuration?: number;
loadingIcon?: ReactNode;
onCopy?: () => Promise<void> | void;
successIcon?: ReactNode;
}
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 shouldReduceMotion = useReducedMotion();
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,
};
const ariaLabels = {
idle: "Copy",
loading: "Copying...",
success: "Copied",
};
return (
<div className="flex justify-center">
<button
aria-label={ariaLabels[buttonState]}
aria-live="polite"
className={`relative min-h-[44px] w-auto min-w-[44px] cursor-pointer overflow-hidden rounded-full border bg-background p-3 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 ${className}`}
disabled={buttonState !== "idle" || disabled}
onClick={handleClick}
type="button"
>
<AnimatePresence initial={false} mode="popLayout">
<motion.span
animate={
shouldReduceMotion
? { opacity: 1 }
: { filter: "blur(0px)", opacity: 1, y: 0 }
}
className="flex w-full items-center justify-center"
exit={
shouldReduceMotion
? { opacity: 0, transition: { duration: 0 } }
: { filter: "blur(10px)", opacity: 0, y: 25 }
}
initial={
shouldReduceMotion
? { opacity: 1 }
: { filter: "blur(10px)", opacity: 0, y: -25 }
}
key={buttonState}
transition={
shouldReduceMotion
? { duration: 0 }
: { bounce: 0, duration: 0.25, type: "spring" as const }
}
>
{icons[buttonState]}
</motion.span>
</AnimatePresence>
</button>
</div>
);
}