96 lines
3.8 KiB
JavaScript
96 lines
3.8 KiB
JavaScript
'use client';
|
|
|
|
import React from 'react';
|
|
|
|
const Button = ({
|
|
children,
|
|
onClick,
|
|
type = 'button',
|
|
variant = 'primary',
|
|
size = 'md',
|
|
disabled = false,
|
|
loading = false,
|
|
icon,
|
|
iconPosition = 'left',
|
|
className = '',
|
|
...props
|
|
}) => {
|
|
const baseClassName = 'cursor-pointer inline-flex items-center justify-center font-medium leading-none rounded-lg transition-all duration-[120ms] ease-out focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed';
|
|
|
|
const variants = {
|
|
primary: 'bg-neutral-900 text-white hover:bg-neutral-800 focus:ring-neutral-900/20 dark:bg-white dark:text-black dark:hover:bg-neutral-100 dark:focus:ring-white/20',
|
|
secondary: 'bg-transparent border border-neutral-300 text-neutral-700 hover:bg-neutral-100 focus:ring-neutral-500/20 dark:bg-neutral-800/60 dark:border-neutral-700/50 dark:text-white dark:hover:bg-neutral-800/80 dark:focus:ring-neutral-600/20',
|
|
danger: 'bg-red-700/10 border border-red-800/30 text-red-700 hover:bg-red-700/15 focus:ring-red-700/20 dark:bg-red-700/20 dark:border-red-600/20 dark:text-red-600 dark:hover:bg-red-600/30 dark:focus:ring-red-600/20',
|
|
ghost: 'text-neutral-600 hover:text-neutral-900 hover:bg-neutral-100 focus:ring-neutral-500/20 dark:text-neutral-400 dark:hover:text-white dark:hover:bg-neutral-700/30 dark:focus:ring-neutral-600/20',
|
|
fullghost: 'text-neutral-600 hover:text-neutral-900 focus:ring-0 dark:text-neutral-400 dark:hover:text-white',
|
|
success: 'bg-green-700/10 border border-green-800/30 text-green-700 hover:bg-green-700/15 focus:ring-green-700/20 dark:bg-green-700/20 dark:border-green-600/20 dark:text-green-600 dark:hover:bg-green-600/30 dark:focus:ring-green-600/20',
|
|
warning: 'bg-yellow-700/10 border border-yellow-800/30 text-yellow-700 hover:bg-yellow-700/15 focus:ring-yellow-700/20 dark:bg-yellow-700/20 dark:border-yellow-600/20 dark:text-yellow-600 dark:hover:bg-yellow-600/30 dark:focus:ring-yellow-600/20'
|
|
};
|
|
|
|
const sizes = {
|
|
sm: 'px-[10px] py-[5px] text-[12px] gap-1',
|
|
md: 'px-[12px] py-[7px] text-[13px] gap-1.5',
|
|
lg: 'px-[14px] py-[9px] text-[14px] gap-2'
|
|
};
|
|
|
|
const iconOnlySizes = {
|
|
sm: 'p-[5px] text-[12px]',
|
|
md: 'p-[7px] text-[13px]',
|
|
lg: 'p-[9px] text-[14px]'
|
|
};
|
|
|
|
const isIconOnly = icon && !children;
|
|
|
|
const iconSizes = {
|
|
sm: 'w-3.5 h-3.5',
|
|
md: 'w-4 h-4',
|
|
lg: 'w-4.5 h-4.5'
|
|
};
|
|
|
|
const textSizes = {
|
|
sm: 'min-h-3.5',
|
|
md: 'min-h-4',
|
|
lg: 'min-h-4.5'
|
|
};
|
|
|
|
const LoadingSpinner = () => (
|
|
<div className={`border-2 border-current border-t-transparent rounded-full animate-spin ${iconSizes[size]}`} />
|
|
);
|
|
|
|
const handleClick = (e) => {
|
|
if (!disabled && !loading && onClick) {
|
|
onClick(e);
|
|
}
|
|
};
|
|
|
|
const Icon = icon;
|
|
|
|
return (
|
|
<button
|
|
type={type}
|
|
onClick={handleClick}
|
|
disabled={disabled || loading}
|
|
className={`${baseClassName} ${variants[variant]} ${isIconOnly ? iconOnlySizes[size] : sizes[size]} ${className}`}
|
|
{...props}
|
|
>
|
|
{loading ? (
|
|
<LoadingSpinner />
|
|
) : (
|
|
<>
|
|
{Icon && iconPosition === 'left' && (
|
|
<Icon className={iconSizes[size]}/>
|
|
)}
|
|
<span className={`${textSizes[size]} flex items-center justify-center`}>
|
|
{children}
|
|
</span>
|
|
|
|
{Icon && iconPosition === 'right' && (
|
|
<Icon className={iconSizes[size]}/>
|
|
)}
|
|
</>
|
|
)}
|
|
</button>
|
|
);
|
|
};
|
|
|
|
export default Button;
|