File
Blob: src/client/components/ui/button.tsx
| 1 | import { forwardRef, type ButtonHTMLAttributes } from "react"; |
| 2 | import { Loader2 } from "lucide-react"; |
| 3 | |
| 4 | type ButtonVariant = "primary" | "secondary" | "danger" | "ghost"; |
| 5 | type ButtonSize = "sm" | "md"; |
| 6 | |
| 7 | interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { |
| 8 | variant?: ButtonVariant; |
| 9 | size?: ButtonSize; |
| 10 | loading?: boolean; |
| 11 | icon?: React.ReactNode; |
| 12 | } |
| 13 | |
| 14 | const 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 | |
| 23 | const 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 | |
| 28 | export 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 | |
| 53 | Button.displayName = "Button"; |