codev / desktop /src /components /shared /Button.tsx
chenbhao's picture
feat: desktop
1f21206
Raw
History Blame
2 kB
import type { ButtonHTMLAttributes, ReactNode } from 'react'
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost'
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: ButtonVariant
size?: 'sm' | 'md' | 'lg'
loading?: boolean
icon?: ReactNode
}
const variantStyles: Record<ButtonVariant, string> = {
primary:
'bg-[image:var(--gradient-btn-primary)] text-[var(--color-btn-primary-fg)] shadow-[var(--shadow-button-primary)] hover:bg-[image:var(--gradient-btn-primary-hover)] hover:brightness-105 active:translate-y-[1px]',
secondary:
'bg-[var(--color-surface)] text-[var(--color-text-primary)] border border-[var(--color-border)] hover:bg-[var(--color-surface-hover)]',
danger:
'bg-[var(--color-error)] text-white hover:opacity-90',
ghost:
'bg-transparent text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)]',
}
const sizeStyles = {
sm: 'px-2 py-1 text-xs',
md: 'px-4 py-2 text-sm',
lg: 'px-5 py-2.5 text-sm',
}
export function Button({
variant = 'primary',
size = 'md',
loading = false,
icon,
disabled,
children,
className = '',
...props
}: ButtonProps) {
return (
<button
disabled={disabled || loading}
className={`
inline-flex items-center justify-center gap-1.5 rounded-[var(--radius-md)]
font-medium transition-colors duration-150 cursor-pointer
disabled:opacity-50 disabled:cursor-not-allowed
${variantStyles[variant]} ${sizeStyles[size]} ${className}
`}
{...props}
>
{loading ? <Spinner /> : icon}
{children}
</button>
)
}
function Spinner() {
return (
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
)
}