diff --git a/devfront/src/components/ui/copy-button.tsx b/devfront/src/components/ui/copy-button.tsx new file mode 100644 index 00000000..83996231 --- /dev/null +++ b/devfront/src/components/ui/copy-button.tsx @@ -0,0 +1,54 @@ +import * as React from "react"; +import { Check, Copy } from "lucide-react"; +import { Button, type ButtonProps } from "./button"; +import { cn } from "../../lib/utils"; + +interface CopyButtonProps extends ButtonProps { + value: string; + onCopy?: () => void; +} + +export function CopyButton({ + value, + onCopy, + className, + variant = "secondary", + size = "icon", + ...props +}: CopyButtonProps) { + const [hasCopied, setHasCopied] = React.useState(false); + + React.useEffect(() => { + if (hasCopied) { + const timer = setTimeout(() => setHasCopied(false), 1500); + return () => clearTimeout(timer); + } + }, [hasCopied]); + + const copyToClipboard = async () => { + try { + await navigator.clipboard.writeText(value); + setHasCopied(true); + if (onCopy) onCopy(); + } catch (err) { + console.error("Failed to copy text: ", err); + } + }; + + return ( + + ); +} diff --git a/devfront/src/components/ui/toaster.tsx b/devfront/src/components/ui/toaster.tsx new file mode 100644 index 00000000..3f951781 --- /dev/null +++ b/devfront/src/components/ui/toaster.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import { useToastState } from "./use-toast"; +import { CheckCircle2, AlertCircle, Info, X } from "lucide-react"; +import { cn } from "../../lib/utils"; + +export function Toaster() { + const toasts = useToastState(); + + if (toasts.length === 0) return null; + + return ( +
{t.message}
+