107 lines
3.1 KiB
JavaScript
107 lines
3.1 KiB
JavaScript
|
|
import { forwardRef } from 'react';
|
||
|
|
|
||
|
|
const SIZE_CLASSES = {
|
||
|
|
sm: 'px-3 py-1.5 text-sm rounded-lg',
|
||
|
|
md: 'px-4 py-2 text-base rounded-xl',
|
||
|
|
lg: 'px-5 py-3 text-base rounded-xl',
|
||
|
|
};
|
||
|
|
|
||
|
|
const Button = forwardRef(function Button(
|
||
|
|
{
|
||
|
|
variant = 'primary',
|
||
|
|
size = 'md',
|
||
|
|
type = 'button',
|
||
|
|
loading = false,
|
||
|
|
leadingIcon,
|
||
|
|
trailingIcon,
|
||
|
|
disabled,
|
||
|
|
className = '',
|
||
|
|
style,
|
||
|
|
children,
|
||
|
|
...rest
|
||
|
|
},
|
||
|
|
ref
|
||
|
|
) {
|
||
|
|
const isDisabled = disabled || loading;
|
||
|
|
const sizeCls = SIZE_CLASSES[size] ?? SIZE_CLASSES.md;
|
||
|
|
|
||
|
|
let variantStyle = {};
|
||
|
|
let variantClass = '';
|
||
|
|
|
||
|
|
if (variant === 'primary') {
|
||
|
|
variantStyle = {
|
||
|
|
background:
|
||
|
|
'linear-gradient(135deg, var(--accent-ember) 0%, var(--accent-flame) 100%)',
|
||
|
|
color: '#ffffff',
|
||
|
|
boxShadow: 'var(--rim-light-inner), var(--ember-rim-pronounced)',
|
||
|
|
};
|
||
|
|
variantClass = 'font-medium transition-transform duration-200 hover:scale-[1.02] active:scale-[0.98]';
|
||
|
|
} else if (variant === 'secondary') {
|
||
|
|
variantStyle = {
|
||
|
|
background: 'var(--glass-surface-high)',
|
||
|
|
backdropFilter:
|
||
|
|
'blur(var(--glass-blur-low)) saturate(var(--glass-saturate))',
|
||
|
|
WebkitBackdropFilter:
|
||
|
|
'blur(var(--glass-blur-low)) saturate(var(--glass-saturate))',
|
||
|
|
color: 'var(--text-primary)',
|
||
|
|
boxShadow: 'var(--rim-light-inner), var(--rim-light-outer)',
|
||
|
|
};
|
||
|
|
variantClass = 'font-medium transition-all duration-200 hover:bg-[var(--glass-surface-mid)]';
|
||
|
|
} else if (variant === 'danger') {
|
||
|
|
variantStyle = {
|
||
|
|
backgroundColor: '#dc2626',
|
||
|
|
color: '#ffffff',
|
||
|
|
boxShadow: 'var(--rim-light-inner)',
|
||
|
|
};
|
||
|
|
variantClass = 'font-medium transition-all duration-200 hover:bg-[#b91c1c]';
|
||
|
|
} else if (variant === 'ghost') {
|
||
|
|
variantStyle = {
|
||
|
|
background: 'transparent',
|
||
|
|
color: 'var(--text-primary)',
|
||
|
|
};
|
||
|
|
variantClass = 'font-medium transition-colors duration-200 hover:bg-[rgba(var(--ember-rim-color),0.08)]';
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<button
|
||
|
|
ref={ref}
|
||
|
|
type={type}
|
||
|
|
disabled={isDisabled}
|
||
|
|
aria-busy={loading || undefined}
|
||
|
|
className={[
|
||
|
|
'relative inline-flex items-center justify-center gap-2 select-none',
|
||
|
|
sizeCls,
|
||
|
|
variantClass,
|
||
|
|
isDisabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : '',
|
||
|
|
'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
||
|
|
className,
|
||
|
|
]
|
||
|
|
.filter(Boolean)
|
||
|
|
.join(' ')}
|
||
|
|
style={{
|
||
|
|
...variantStyle,
|
||
|
|
'--tw-ring-color': 'var(--accent-ember)',
|
||
|
|
'--tw-ring-offset-color': 'transparent',
|
||
|
|
...style,
|
||
|
|
}}
|
||
|
|
{...rest}
|
||
|
|
>
|
||
|
|
{loading && (
|
||
|
|
<span
|
||
|
|
aria-hidden="true"
|
||
|
|
className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-r-transparent"
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
{!loading && leadingIcon && (
|
||
|
|
<span aria-hidden="true" className="inline-flex">{leadingIcon}</span>
|
||
|
|
)}
|
||
|
|
<span>{children}</span>
|
||
|
|
{!loading && trailingIcon && (
|
||
|
|
<span aria-hidden="true" className="inline-flex">{trailingIcon}</span>
|
||
|
|
)}
|
||
|
|
</button>
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
export default Button;
|