Button.tsx
1 /** 2 * ACDC Button Component 3 * Chain-aware button with all variants 4 */ 5 6 import React from 'react' 7 import type { Chain } from '../tokens' 8 9 export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { 10 variant?: 'primary' | 'secondary' | 'ghost' | 'destructive' | 'success' 11 size?: 'sm' | 'md' | 'lg' 12 chain?: Chain 13 loading?: boolean 14 children: React.ReactNode 15 } 16 17 export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 18 ( 19 { 20 variant = 'primary', 21 size = 'md', 22 chain, 23 loading = false, 24 disabled, 25 className = '', 26 children, 27 ...props 28 }, 29 ref 30 ) => { 31 // Wise-inspired: pill shapes, generous padding, smooth transitions 32 const baseStyles = ` 33 inline-flex items-center justify-center 34 font-semibold tracking-tight 35 transition-all duration-300 ease-out 36 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 37 disabled:opacity-50 disabled:pointer-events-none 38 ` 39 40 const sizeStyles = { 41 sm: 'h-9 px-4 text-sm rounded-full', 42 md: 'h-11 px-6 text-base rounded-full', 43 lg: 'h-14 px-8 text-lg rounded-full', 44 } 45 46 // Wise-inspired: clean colors, subtle hover lift, soft shadows 47 const variantStyles = { 48 primary: ` 49 bg-[var(--btn-primary-bg)] text-white 50 shadow-sm 51 hover:bg-[var(--btn-primary-bg-hover)] 52 hover:shadow-md hover:-translate-y-0.5 53 active:bg-[var(--btn-primary-bg-active)] 54 active:translate-y-0 active:shadow-sm 55 `, 56 secondary: ` 57 bg-white/10 backdrop-blur-sm 58 border-2 border-[var(--border-default)] 59 text-[var(--text-primary)] 60 hover:border-[var(--btn-secondary-border-hover)] 61 hover:bg-white/20 62 hover:-translate-y-0.5 63 active:translate-y-0 64 `, 65 ghost: ` 66 bg-transparent text-[var(--text-secondary)] 67 hover:bg-[var(--bg-tertiary)] 68 hover:text-[var(--text-primary)] 69 `, 70 destructive: ` 71 bg-[var(--error)] text-white 72 shadow-sm 73 hover:bg-[var(--error-hover)] 74 hover:shadow-md hover:-translate-y-0.5 75 active:bg-[var(--error-active)] 76 active:translate-y-0 active:shadow-sm 77 `, 78 success: ` 79 bg-[var(--success)] text-white 80 shadow-sm 81 hover:bg-[var(--success-hover)] 82 hover:shadow-md hover:-translate-y-0.5 83 active:bg-[var(--success-active)] 84 active:translate-y-0 active:shadow-sm 85 `, 86 } 87 88 return ( 89 <button 90 ref={ref} 91 data-chain={chain} 92 disabled={disabled || loading} 93 className={`${baseStyles} ${sizeStyles[size]} ${variantStyles[variant]} ${className}`} 94 {...props} 95 > 96 {loading ? ( 97 <> 98 <svg 99 className="animate-spin -ml-1 mr-2 h-4 w-4" 100 fill="none" 101 viewBox="0 0 24 24" 102 > 103 <circle 104 className="opacity-25" 105 cx="12" 106 cy="12" 107 r="10" 108 stroke="currentColor" 109 strokeWidth="4" 110 /> 111 <path 112 className="opacity-75" 113 fill="currentColor" 114 d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" 115 /> 116 </svg> 117 Loading... 118 </> 119 ) : ( 120 children 121 )} 122 </button> 123 ) 124 } 125 ) 126 127 Button.displayName = 'Button'