File
Blob: src/client/components/ui/button.tsx
| 1 | import type { ButtonHTMLAttributes, ReactNode } from "react"; |
| 2 | import { Link, type LinkProps } from "react-router-dom"; |
| 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?: ReactNode; |
| 12 | } |
| 13 | |
| 14 | const VARIANT_CLASSES: Record<ButtonVariant, string> = { |
| 15 | primary: "bg-accent-600 text-white hover:bg-accent-500 active:scale-[0.98] transition-transform", |
| 16 | secondary: |
| 17 | "border border-zinc-700/60 bg-zinc-800/60 text-zinc-300 hover:bg-zinc-700/60 hover:text-zinc-100 active:scale-[0.98] transition-transform", |
| 18 | danger: |
| 19 | "border border-red-500/20 bg-red-500/10 text-red-400 hover:bg-red-500/20 hover:text-red-300 active:scale-[0.98] transition-transform", |
| 20 | ghost: "text-zinc-400 hover:bg-zinc-800/70 hover:text-zinc-100 active:scale-[0.97] transition-transform", |
| 21 | }; |
| 22 | |
| 23 | const SIZE_CLASSES: Record<ButtonSize, string> = { |
| 24 | sm: "rounded-lg px-3 py-1.5 text-xs", |
| 25 | md: "rounded-xl px-4 py-2.5 text-sm", |
| 26 | }; |
| 27 | |
| 28 | export const Button = ({ |
| 29 | variant = "secondary", |
| 30 | size = "md", |
| 31 | loading = false, |
| 32 | icon, |
| 33 | children, |
| 34 | disabled, |
| 35 | className, |
| 36 | ...rest |
| 37 | }: ButtonProps) => ( |
| 38 | <button |
| 39 | type="button" |
| 40 | disabled={disabled || loading} |
| 41 | className={[ |
| 42 | "inline-flex items-center justify-center gap-2 font-medium disabled:cursor-not-allowed disabled:opacity-50", |
| 43 | SIZE_CLASSES[size], |
| 44 | VARIANT_CLASSES[variant], |
| 45 | className, |
| 46 | ] |
| 47 | .filter(Boolean) |
| 48 | .join(" ")} |
| 49 | {...rest} |
| 50 | > |
| 51 | {icon} |
| 52 | {loading ? "Loading..." : children} |
| 53 | </button> |
| 54 | ); |
| 55 | |
| 56 | interface ButtonLinkProps extends LinkProps { |
| 57 | variant?: ButtonVariant; |
| 58 | size?: ButtonSize; |
| 59 | } |
| 60 | |
| 61 | export const ButtonLink = ({ variant = "secondary", size = "md", className, children, ...rest }: ButtonLinkProps) => ( |
| 62 | <Link |
| 63 | className={[ |
| 64 | "inline-flex items-center justify-center gap-2 font-medium", |
| 65 | SIZE_CLASSES[size], |
| 66 | VARIANT_CLASSES[variant], |
| 67 | className, |
| 68 | ] |
| 69 | .filter(Boolean) |
| 70 | .join(" ")} |
| 71 | {...rest} |
| 72 | > |
| 73 | {children} |
| 74 | </Link> |
| 75 | ); |