| import { LucideIcon } from 'lucide-react'; |
| import * as React from 'react'; |
| import { IconType } from 'react-icons'; |
| import { ImSpinner2 } from 'react-icons/im'; |
|
|
| import { cn } from '@/lib/utils'; |
|
|
| const IconButtonVariant = [ |
| 'primary', |
| 'outline', |
| 'ghost', |
| 'light', |
| 'dark', |
| ] as const; |
|
|
| type IconButtonProps = { |
| isLoading?: boolean; |
| isDarkBg?: boolean; |
| variant?: (typeof IconButtonVariant)[number]; |
| icon?: IconType | LucideIcon; |
| classNames?: { |
| icon?: string; |
| }; |
| } & React.ComponentPropsWithRef<'button'>; |
|
|
| const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>( |
| ( |
| { |
| className, |
| disabled: buttonDisabled, |
| isLoading, |
| variant = 'primary', |
| isDarkBg = false, |
| icon: Icon, |
| classNames, |
| ...rest |
| }, |
| ref |
| ) => { |
| const disabled = isLoading || buttonDisabled; |
|
|
| return ( |
| <button |
| ref={ref} |
| type='button' |
| disabled={disabled} |
| className={cn( |
| 'inline-flex items-center justify-center rounded font-medium', |
| 'focus-visible:ring-primary-500 focus:outline-none focus-visible:ring', |
| 'shadow-sm', |
| 'transition-colors duration-75', |
| 'min-h-[28px] min-w-[28px] p-1 md:min-h-[34px] md:min-w-[34px] md:p-2', |
| //#region //*=========== Variants =========== |
| [ |
| variant === 'primary' && [ |
| 'bg-primary-500 text-white', |
| 'border-primary-600 border', |
| 'hover:bg-primary-600 hover:text-white', |
| 'active:bg-primary-700', |
| 'disabled:bg-primary-700', |
| ], |
| variant === 'outline' && [ |
| 'text-primary-500', |
| 'border-primary-500 border', |
| 'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100', |
| isDarkBg && |
| 'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800', |
| ], |
| variant === 'ghost' && [ |
| 'text-primary-500', |
| 'shadow-none', |
| 'hover:bg-primary-50 active:bg-primary-100 disabled:bg-primary-100', |
| isDarkBg && |
| 'hover:bg-gray-900 active:bg-gray-800 disabled:bg-gray-800', |
| ], |
| variant === 'light' && [ |
| 'bg-white text-gray-700', |
| 'border border-gray-300', |
| 'hover:text-dark hover:bg-gray-100', |
| 'active:bg-white/80 disabled:bg-gray-200', |
| ], |
| variant === 'dark' && [ |
| 'bg-gray-900 text-white', |
| 'border border-gray-600', |
| 'hover:bg-gray-800 active:bg-gray-700 disabled:bg-gray-700', |
| ], |
| ], |
| //#endregion //*======== Variants =========== |
| 'disabled:cursor-not-allowed', |
| isLoading && |
| 'relative text-transparent transition-none hover:text-transparent disabled:cursor-wait', |
| className |
| )} |
| {...rest} |
| > |
| {isLoading && ( |
| <div |
| className={cn( |
| 'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2', |
| { |
| 'text-white': ['primary', 'dark'].includes(variant), |
| 'text-black': ['light'].includes(variant), |
| 'text-primary-500': ['outline', 'ghost'].includes(variant), |
| } |
| )} |
| > |
| <ImSpinner2 className='animate-spin' /> |
| </div> |
| )} |
| {Icon && <Icon size='1em' className={cn(classNames?.icon)} />} |
| </button> |
| ); |
| } |
| ); |
|
|
| export default IconButton; |
|
|