Copy Button
A versatile copy button component with support for animations and different states, built on top of the Shadcn Button.
Installation
Run the following command to add the copy-button component to your project using the Flow UI registry:
npx shadcn@latest add @flowui/copy-buttonInstall Dependencies
Ensure you have lucide-react and motion (Framer Motion) installed.
npm install lucide-react motionInstall Button Component
This component is built on top of the Shadcn UI Button. If you haven't installed it yet, run:
npx shadcn@latest add buttonCreate Animation Wrapper
Create a helper component animation-wrapper.tsx for handling animations.
import { motion, AnimatePresence } from "motion/react";
type AnimatedLogicProps = {
children: React.ReactNode;
mode: "copy" | "copied";
};
const AnimationWrapper = ({ children, mode }: AnimatedLogicProps) => {
return (
<AnimatePresence mode="wait">
<motion.div
key={mode}
initial={{ scale: 0.6, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.6, opacity: 0 }}
>
{children}
</motion.div>
</AnimatePresence>
)
}
export default AnimationWrapper;Create Copy Button
Create the main component copy-button.tsx.
"use client";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Check, Copy } from "lucide-react";
import { useState } from "react";
import AnimationWrapper from "./animation-wrapper";
type AnimationWrapperProps = {
children: React.ReactNode;
mode: "copy" | "copied";
};
export type CopyButtonProps = {
animated?: boolean;
time?: number;
iconType?: "default" | "rotated";
} & React.ComponentProps<typeof Button>;
export const CopyButton = ({
animated = false,
className,
children,
time = 3000,
iconType = "default",
...props
}: CopyButtonProps) => {
// Fixed Logic for any behavior
const [mode, setMode] = useState<"copy" | "copied">("copy");
const handleCopy = (e: React.MouseEvent<HTMLButtonElement>) => {
if (props.onClick) {
props.onClick(e);
}
// Change state to copied
setMode("copied");
// Change to the base state after user timer - default 3s
const timer = setTimeout(() => {
setMode("copy");
}, time);
// Clear the timer
return () => clearTimeout(timer);
}
// Identify the icon to be used and create it's node
const Icon = mode === "copy" ? Copy : Check;
const iconNode = (
<Icon
className={cn(
"size-4 shrink-0",
iconType === "rotated" && mode === "copy" && "rotate-90"
)}
/>
);
const content = animated ? (
<AnimationWrapper mode={mode}>
{iconNode}
</AnimationWrapper>
) : (
iconNode
)
return (
<Button
{...props}
onClick={handleCopy}
className={cn("relative", className)}
>
{content}
</Button>
)
}Create Animated Wrapper (Optional)
For convenience, you can create animated-copy-button.tsx to pre-configure the animated version.
import { CopyButton, CopyButtonProps } from "./copy-button"
export const AnimatedCopyButton = (props: CopyButtonProps) => {
return (
<CopyButton
{...props}
animated={true}
/>
)
}Usage
Basic Usage
import { CopyButton } from "@/components/ui/copy-button/copy-button";
export function BasicExample() {
return (
<div className="flex items-center gap-4">
<CopyButton variant="outline">
Click to Copy
</CopyButton>
<CopyButton variant="default" className="rounded-full">
Rounded Copy
</CopyButton>
</div>
)
}Animated Usage
import { AnimatedCopyButton } from "@/components/ui/copy-button/animated-copy-button";
export function AnimatedExample() {
return (
<div className="flex items-center gap-4">
<AnimatedCopyButton variant="outline">
Animated Copy
</AnimatedCopyButton>
<AnimatedCopyButton variant="secondary" iconType="rotated">
Rotated Icon
</AnimatedCopyButton>
</div>
)
}Props
The CopyButton component accepts all props from the standard Shadcn Button component, plus the following:
| Prop | Type | Default | Description |
|---|---|---|---|
animated | boolean | false | Whether to animate the icon transition between copy and check states. |
time | number | 3000 | The duration in milliseconds before the icon reverts to the copy state. |
iconType | "default" | "rotated" | "default" | The style of the copy icon. "rotated" adds a rotation effect in the default state. |
Note
The CopyButton does not automatically handle the actual clipboard copying logic (e.g., navigator.clipboard.writeText). You should implement the copy logic within the onClick handler, which is passed through to the underlying Button.
Auth Buttons
Everytime building auth components, need to search or find logos ? Why, not directly use FlowUI Auth Buttons, with pre-built styling. So, daily problem solved ?
Debounced Input
An enhanced Input component that delays value updates, perfect for search fields and performance-sensitive forms.