67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
// components/ui/Button.tsx
|
|
import type { ReactNode } from "react";
|
|
import { Loader2 } from "lucide-react";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
icon?: ReactNode;
|
|
variant?: "primary" | "secondary" | "ghost" | "danger";
|
|
loading?: boolean;
|
|
loadingText?: string;
|
|
fullWidth?: boolean;
|
|
rounded?: boolean | "md" | "lg" | "xl" | "2xl" | "full";
|
|
};
|
|
|
|
export function Button({
|
|
icon,
|
|
variant = "primary",
|
|
children,
|
|
className = "",
|
|
type = "button",
|
|
loading = false,
|
|
loadingText,
|
|
disabled,
|
|
fullWidth = false,
|
|
rounded = "2xl",
|
|
...rest
|
|
}: ButtonProps) {
|
|
const base = twMerge(
|
|
"inline-flex items-center justify-center gap-2 px-5 py-2.5 font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md",
|
|
fullWidth && "w-full",
|
|
typeof rounded === "string" ? `rounded-${rounded}` : rounded === true ? "rounded-md" : "rounded-2xl"
|
|
);
|
|
|
|
const variants = {
|
|
primary:
|
|
"bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 dark:bg-blue-600 dark:hover:bg-blue-500",
|
|
secondary:
|
|
"bg-zinc-100 text-zinc-900 hover:bg-zinc-200 focus:ring-zinc-400 dark:bg-zinc-700 dark:text-white dark:hover:bg-zinc-600",
|
|
ghost:
|
|
"bg-transparent text-zinc-700 hover:bg-zinc-100 focus:ring-zinc-300 dark:text-zinc-200 dark:hover:bg-zinc-800",
|
|
danger:
|
|
"bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 dark:bg-red-600 dark:hover:bg-red-500",
|
|
};
|
|
|
|
return (
|
|
<button
|
|
type={type}
|
|
className={twMerge(base, variants[variant], className)}
|
|
disabled={disabled || loading}
|
|
aria-busy={loading}
|
|
{...rest}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<Loader2 className="animate-spin w-4 h-4" />
|
|
{loadingText && <span>{loadingText}</span>}
|
|
</>
|
|
) : (
|
|
<>
|
|
{icon && <span className="flex items-center">{icon}</span>}
|
|
{children}
|
|
</>
|
|
)}
|
|
</button>
|
|
);
|
|
}
|