button.tsx
1 import { Slot } from "@radix-ui/react-slot"; 2 import { cva, type VariantProps } from "class-variance-authority"; 3 import { cn, focusRing } from "../../lib/utils"; 4 import * as React from "react"; 5 6 const buttonVariants = cva( 7 "ml-1px inline-flex items-center justify-center whitespace-nowrap font-medium transition-colors whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50", 8 { 9 compoundVariants: [ 10 { 11 variant: "default", 12 color: "muted", 13 className: "text-foreground hover:text-background", 14 }, 15 { 16 variant: "outline", 17 color: "muted", 18 className: "text-gray-500", 19 }, 20 { 21 variant: ["outline", "default"], 22 size: "xl", 23 className: "border-3", 24 }, 25 { 26 variant: "filled", 27 color: "primary", 28 className: "bg-primary", 29 }, 30 { 31 variant: "filled", 32 color: "secondary", 33 className: "bg-secondary", 34 }, 35 { 36 variant: "filled", 37 color: "error", 38 className: "bg-error", 39 }, 40 { 41 variant: "filled", 42 color: "success", 43 className: "bg-success", 44 }, 45 { 46 variant: "filled", 47 color: "warning", 48 className: "bg-warning", 49 }, 50 { 51 variant: "filled", 52 color: "info", 53 className: "bg-info", 54 }, 55 { 56 asIconButton: true, 57 size: "xs", 58 className: "w-5", 59 }, 60 { 61 asIconButton: true, 62 size: "sm", 63 className: "w-6", 64 }, 65 { 66 asIconButton: true, 67 size: "md", 68 className: "w-8", 69 }, 70 { 71 asIconButton: true, 72 size: "lg", 73 className: "w-10", 74 }, 75 { 76 asIconButton: true, 77 size: "xl", 78 className: "w-12", 79 }, 80 { 81 variant: "ghost", 82 color: ["primary", "secondary", "error", "success", "warning", "info"], 83 className: "bg-transparent", 84 }, 85 ], 86 variants: { 87 color: { 88 primary: 89 "bg-primary/75 border-primary text-primary hover:bg-primary/50 focus:ring-primary", 90 secondary: 91 "bg-secondary/75 border-secondary text-secondary hover:bg-secondary/50 focus:ring-secondary", 92 error: 93 "bg-error/75 border-error text-error hover:bg-error/50 focus:ring-error", 94 success: 95 "bg-success/75 border-success text-success hover:bg-success/50 focus:ring-success", 96 warning: 97 "bg-warning/75 border-warning text-warning hover:bg-warning/50 focus:ring-warning", 98 info: "bg-info/75 border-info text-info hover:bg-info/50 focus:ring-info", 99 muted: "border-gray-500 hover:bg-gray-400/50", 100 }, 101 variant: { 102 default: "border-2 shadow text-foreground hover:border-foreground", 103 filled: 104 "shadow text-foreground hover:bg-foreground hover:text-background", 105 outline: 106 "border-2 shadow bg-transparent hover:text-foreground hover:border-foreground", 107 ghost: "shadow hover:bg-accent hover:text-accent-foreground", 108 link: "bg-transparent hover:bg-transparent underline focus:ring-offset-0", 109 }, 110 size: { 111 xs: "h-5 px-2 text-xs", 112 sm: "h-6 px-3 text-sm", 113 md: "h-8 px-4 text-base", 114 lg: "h-10 px-8 text-lg", 115 xl: "h-12 px-10 text-xl", 116 }, 117 round: { 118 none: "rounded-none", 119 both: "rounded-full", 120 left: "rounded-l-full", 121 right: "rounded-r-full", 122 t: "rounded-t", 123 b: "rounded-b", 124 tl: "rounded-tl", 125 tr: "rounded-tr", 126 bl: "rounded-bl", 127 br: "rounded-br", 128 }, 129 asIconButton: { 130 true: "p-0", 131 }, 132 noShadow: { 133 true: "shadow-none", 134 }, 135 }, 136 defaultVariants: { 137 variant: "default", 138 color: "primary", 139 round: "both", 140 size: "md", 141 }, 142 }, 143 ); 144 145 export interface ButtonProps 146 extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color">, 147 VariantProps<typeof buttonVariants> { 148 asChild?: boolean; 149 height?: string; 150 asIconButton?: boolean; 151 noShadow?: boolean; 152 isLoading?: boolean; 153 } 154 155 const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 156 ( 157 { 158 className, 159 children, 160 color, 161 variant, 162 round, 163 size, 164 asIconButton, 165 height, 166 noShadow, 167 isLoading, 168 asChild = false, 169 ...props 170 }, 171 ref, 172 ) => { 173 const Comp = asChild ? Slot : "button"; 174 175 return ( 176 <Comp 177 className={cn( 178 focusRing, 179 buttonVariants({ 180 variant, 181 color, 182 size, 183 round, 184 asIconButton, 185 noShadow, 186 }), 187 className, 188 size && size !== "xs" && `rounded-${round}-${size}`, 189 height, 190 isLoading && "animate-border-pulse", 191 )} 192 ref={ref} 193 disabled={isLoading} 194 {...props} 195 > 196 {children} 197 </Comp> 198 ); 199 }, 200 ); 201 Button.displayName = "Button"; 202 203 export { Button, buttonVariants };