Skip to content
File

Blob: src/client/components/ui/button.tsx

typescript54 lines
1import { forwardRef, type ButtonHTMLAttributes } from "react";
2import { Loader2 } from "lucide-react";
3 
4type ButtonVariant = "primary" | "secondary" | "danger" | "ghost";
5type ButtonSize = "sm" | "md";
6 
7interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
8 variant?: ButtonVariant;
9 size?: ButtonSize;
10 loading?: boolean;
11 icon?: React.ReactNode;
12}
13 
14const variantClasses: Record<ButtonVariant, string> = {
15 primary: "bg-accent-600 text-white hover:bg-accent-500 active:scale-[0.98] transition-colors",
16 secondary:
17 "border border-zinc-700/60 bg-zinc-800/60 text-zinc-300 hover:bg-zinc-700/60 active:scale-[0.98] transition-colors",
18 danger:
19 "border border-red-500/20 bg-red-500/10 text-red-400 hover:bg-red-500/20 active:scale-[0.98] transition-colors",
20 ghost: "text-zinc-400 hover:bg-zinc-800/70 hover:text-zinc-100 active:scale-[0.97] transition-colors",
21};
22 
23const sizeClasses: Record<ButtonSize, string> = {
24 sm: "px-2 py-0.5 text-xs gap-1 rounded-md",
25 md: "px-3 py-1.5 text-sm gap-1.5 rounded-lg",
26};
27 
28export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
29 (
30 { variant = "secondary", size = "sm", loading = false, icon, children, disabled, className = "", ...props },
31 ref,
32 ) => {
33 return (
34 <button
35 ref={ref}
36 disabled={disabled || loading}
37 className={`inline-flex items-center justify-center font-medium disabled:opacity-40 disabled:pointer-events-none ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
38 {...props}
39 >
40 {loading ? (
41 <Loader2 size={size === "sm" ? 12 : 14} className="animate-spin" aria-hidden="true" />
42 ) : icon ? (
43 <span className="shrink-0" aria-hidden="true">
44 {icon}
45 </span>
46 ) : null}
47 {children}
48 </button>
49 );
50 },
51);
52 
53Button.displayName = "Button";