Mirrored from GitHub github.com/roostorg/coop
0

Configure Feed

Select the types of activity you want to include in your feed.

coop / client / src / coop-ui / Button.tsx
11 kB 319 lines
1import { cn } from '@/lib/utils'; 2import { Slot } from '@radix-ui/react-slot'; 3import { cva, type VariantProps } from 'class-variance-authority'; 4import { LoaderCircle } from 'lucide-react'; 5import * as React from 'react'; 6 7const buttonVariants = cva( 8 'py-3 px-4 inline-flex items-center gap-x-2 text-sm font-semibold rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-none', 9 { 10 variants: { 11 variant: { 12 default: 'border border-transparent', 13 outline: 'border', 14 ghost: 'border border-transparent', 15 soft: 'border border-transparent', 16 white: 17 'border border-gray-200 bg-white hover:bg-gray-50 focus:bg-gray-50 dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700', 18 link: 'border border-transparent hover:underline', 19 }, 20 color: { 21 gray: '', 22 indigo: '', 23 red: '', 24 yellow: '', 25 teal: '', 26 }, 27 size: { 28 default: 'py-3 px-4', 29 sm: 'py-2 px-3 text-sm', 30 lg: 'py-4 px-5 text-base', 31 icon: 'p-2', 32 }, 33 }, 34 compoundVariants: [ 35 // Default variant compounds 36 { 37 variant: 'default', 38 color: 'gray', 39 class: 40 'bg-gray-800 text-white hover:bg-gray-900 focus:bg-gray-900 dark:bg-white dark:text-neutral-800', 41 }, 42 { 43 variant: 'default', 44 color: 'indigo', 45 class: 46 'bg-indigo-500 text-white hover:bg-indigo-700 focus:bg-indigo-700', 47 }, 48 { 49 variant: 'default', 50 color: 'red', 51 class: 'bg-red-600 text-white hover:bg-red-700 focus:bg-red-700', 52 }, 53 { 54 variant: 'default', 55 color: 'yellow', 56 class: 57 'bg-yellow-500 text-white hover:bg-yellow-700 focus:bg-yellow-700', 58 }, 59 { 60 variant: 'default', 61 color: 'teal', 62 class: 'bg-teal-500 text-white hover:bg-teal-700 focus:bg-teal-700', 63 }, 64 65 // Outline variant compounds 66 { 67 variant: 'outline', 68 color: 'gray', 69 class: 70 'border-gray-600 text-gray-600 hover:bg-gray-50 hover:border-gray-800 hover:text-gray-800 focus:border-gray-800 focus:text-gray-800 dark:border-gray-500 dark:text-gray-500 dark:hover:border-gray-700 dark:hover:text-gray-700 dark:focus:border-gray-700 dark:focus:text-gray-700', 71 }, 72 { 73 variant: 'outline', 74 color: 'indigo', 75 class: 76 'border-indigo-600 text-indigo-600 hover:bg-indigo-50 hover:border-indigo-800 hover:text-indigo-800 focus:border-indigo-800 focus:text-indigo-800 dark:border-indigo-500 dark:text-indigo-500 dark:hover:border-indigo-700 dark:hover:text-indigo-700 dark:focus:border-indigo-700 dark:focus:text-indigo-700', 77 }, 78 { 79 variant: 'outline', 80 color: 'red', 81 class: 82 'border-red-600 text-red-600 hover:bg-red-50 hover:border-red-800 hover:text-red-800 focus:border-red-800 focus:text-red-800 dark:border-red-500 dark:text-red-500 dark:hover:border-red-700 dark:hover:text-red-700 dark:focus:border-red-700 dark:focus:text-red-700', 83 }, 84 { 85 variant: 'outline', 86 color: 'yellow', 87 class: 88 'border-yellow-600 text-yellow-600 hover:bg-yellow-50 hover:border-yellow-800 hover:text-yellow-800 focus:border-yellow-800 focus:text-yellow-800 dark:border-yellow-500 dark:text-yellow-500 dark:hover:border-yellow-700 dark:hover:text-yellow-700 dark:focus:border-yellow-700 dark:focus:text-yellow-700', 89 }, 90 { 91 variant: 'outline', 92 color: 'teal', 93 class: 94 'border-teal-600 text-teal-600 hover:bg-teal-50 hover:border-teal-800 hover:text-teal-800 focus:border-teal-800 focus:text-teal-800 dark:border-teal-500 dark:text-teal-500 dark:hover:border-teal-700 dark:hover:text-teal-700 dark:focus:border-teal-700 dark:focus:text-teal-700', 95 }, 96 97 // Ghost variant compounds 98 { 99 variant: 'ghost', 100 color: 'gray', 101 class: 102 'text-gray-600 hover:bg-gray-100 hover:text-gray-800 focus:bg-gray-100 focus:text-gray-800 dark:text-gray-500 dark:hover:bg-gray-800/30 dark:hover:text-gray-400 dark:focus:bg-gray-800/30 dark:focus:text-gray-400', 103 }, 104 { 105 variant: 'ghost', 106 color: 'indigo', 107 class: 108 'text-indigo-600 hover:bg-indigo-100 hover:text-indigo-800 focus:bg-indigo-100 focus:text-indigo-800 dark:text-indigo-500 dark:hover:bg-indigo-800/30 dark:hover:text-indigo-400 dark:focus:bg-indigo-800/30 dark:focus:text-indigo-400', 109 }, 110 { 111 variant: 'ghost', 112 color: 'red', 113 class: 114 'text-red-600 hover:bg-red-100 hover:text-red-800 focus:bg-red-100 focus:text-red-800 dark:text-red-500 dark:hover:bg-red-800/30 dark:hover:text-red-400 dark:focus:bg-red-800/30 dark:focus:text-red-400', 115 }, 116 { 117 variant: 'ghost', 118 color: 'yellow', 119 class: 120 'text-yellow-600 hover:bg-yellow-100 hover:text-yellow-800 focus:bg-yellow-100 focus:text-yellow-800 dark:text-yellow-500 dark:hover:bg-yellow-800/30 dark:hover:text-yellow-400 dark:focus:bg-yellow-800/30 dark:focus:text-yellow-400', 121 }, 122 { 123 variant: 'ghost', 124 color: 'teal', 125 class: 126 'text-teal-600 hover:bg-teal-100 hover:text-teal-800 focus:bg-teal-100 focus:text-teal-800 dark:text-teal-500 dark:hover:bg-teal-800/30 dark:hover:text-teal-400 dark:focus:bg-teal-800/30 dark:focus:text-teal-400', 127 }, 128 129 // Soft variant compounds 130 { 131 variant: 'soft', 132 color: 'gray', 133 class: 134 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:bg-gray-200 dark:text-gray-400 dark:hover:bg-gray-900 dark:focus:bg-gray-900', 135 }, 136 { 137 variant: 'soft', 138 color: 'indigo', 139 class: 140 'bg-indigo-100 text-indigo-900 hover:bg-indigo-200 focus:bg-indigo-200 dark:text-indigo-400 dark:hover:bg-indigo-900 dark:focus:bg-indigo-900', 141 }, 142 { 143 variant: 'soft', 144 color: 'red', 145 class: 146 'bg-red-100 text-red-900 hover:bg-red-200 focus:bg-red-200 dark:text-red-400 dark:hover:bg-red-900 dark:focus:bg-red-900', 147 }, 148 { 149 variant: 'soft', 150 color: 'yellow', 151 class: 152 'bg-yellow-100 text-yellow-900 hover:bg-yellow-200 focus:bg-yellow-200 dark:text-yellow-400 dark:hover:bg-yellow-900 dark:focus:bg-yellow-900', 153 }, 154 { 155 variant: 'soft', 156 color: 'teal', 157 class: 158 'bg-teal-100 text-teal-900 hover:bg-teal-200 focus:bg-teal-200 dark:text-teal-400 dark:hover:bg-teal-900 dark:focus:bg-teal-900', 159 }, 160 161 // White variant compounds 162 { 163 variant: 'white', 164 color: 'gray', 165 class: 'text-gray-800 dark:text-white', 166 }, 167 { 168 variant: 'white', 169 color: 'indigo', 170 class: 'text-indigo-600 dark:text-indigo-500', 171 }, 172 { 173 variant: 'white', 174 color: 'red', 175 class: 'text-red-600 dark:text-red-500', 176 }, 177 { 178 variant: 'white', 179 color: 'yellow', 180 class: 'text-yellow-600 dark:text-yellow-500', 181 }, 182 { 183 variant: 'white', 184 color: 'teal', 185 class: 'text-teal-600 dark:text-teal-500', 186 }, 187 188 // Link variant compounds 189 { 190 variant: 'link', 191 color: 'gray', 192 class: 193 'text-gray-600 hover:text-gray-800 focus:text-gray-800 dark:text-gray-500 dark:hover:text-gray-400 dark:focus:text-gray-400', 194 }, 195 { 196 variant: 'link', 197 color: 'indigo', 198 class: 199 'text-indigo-600 hover:text-indigo-800 focus:text-indigo-800 dark:text-indigo-500 dark:hover:text-indigo-400 dark:focus:text-indigo-400', 200 }, 201 { 202 variant: 'link', 203 color: 'red', 204 class: 205 'text-red-600 hover:text-red-800 focus:text-red-800 dark:text-red-500 dark:hover:text-red-400 dark:focus:text-red-400', 206 }, 207 { 208 variant: 'link', 209 color: 'yellow', 210 class: 211 'text-yellow-600 hover:text-yellow-800 focus:text-yellow-800 dark:text-yellow-500 dark:hover:text-yellow-400 dark:focus:text-yellow-400', 212 }, 213 { 214 variant: 'link', 215 color: 'teal', 216 class: 217 'text-teal-600 hover:text-teal-800 focus:text-teal-800 dark:text-teal-500 dark:hover:text-teal-400 dark:focus:text-teal-400', 218 }, 219 ], 220 defaultVariants: { 221 variant: 'default', 222 color: 'indigo', 223 size: 'default', 224 }, 225 }, 226); 227 228export type ButtonVariant = VariantProps<typeof buttonVariants>['variant']; 229export type ButtonColor = VariantProps<typeof buttonVariants>['color']; 230export type ButtonSize = VariantProps<typeof buttonVariants>['size']; 231 232export interface ButtonProps extends Omit< 233 React.ButtonHTMLAttributes<HTMLButtonElement>, 234 'color' 235> { 236 variant?: ButtonVariant; 237 color?: ButtonColor; 238 size?: ButtonSize; 239 asChild?: boolean; 240 loading?: boolean; 241 startIcon?: React.JSXElementConstructor<React.SVGProps<SVGSVGElement>>; 242 endIcon?: React.JSXElementConstructor<React.SVGProps<SVGSVGElement>>; 243} 244 245const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 246 ( 247 { 248 className, 249 variant, 250 color, 251 size, 252 asChild = false, 253 loading = false, 254 startIcon: StartIcon, 255 endIcon: EndIcon, 256 children, 257 ...props 258 }, 259 ref, 260 ) => { 261 const Comp = asChild ? Slot : 'button'; 262 263 const finalColor = 264 variant === 'white' && !color ? 'gray' : (color ?? 'indigo'); 265 266 const content = ( 267 <> 268 {loading && ( 269 <LoaderCircle 270 data-testid="loading-spinner" 271 className="h-4 w-4 animate-spin" 272 /> 273 )} 274 275 {!loading && StartIcon && ( 276 <span 277 data-testid="start-icon" 278 className="flex items-center justify-center mr-1" 279 > 280 {<StartIcon className="w-4 h-4" />} 281 </span> 282 )} 283 284 {size === 'icon' && 285 React.isValidElement(children) && 286 React.cloneElement(children, { 287 ...children.props, 288 className: cn(children.props.className, 'size-4'), 289 })} 290 291 {size !== 'icon' && children} 292 293 {!loading && EndIcon && ( 294 <span className="ml-1" data-testid="end-icon"> 295 {<EndIcon className="w-4 h-4" />} 296 </span> 297 )} 298 </> 299 ); 300 301 return ( 302 <Comp 303 className={cn( 304 buttonVariants({ variant, size, color: finalColor }), 305 className, 306 loading && 'pointer-events-none', 307 )} 308 ref={ref} 309 disabled={loading || props.disabled} 310 {...props} 311 > 312 {content} 313 </Comp> 314 ); 315 }, 316); 317Button.displayName = 'Button'; 318 319export { Button, buttonVariants };