button.tsx 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. import { Button as BS5Button, Spinner } from 'react-bootstrap-5'
  2. import type { ButtonProps } from '@/features/ui/components/types/button-props'
  3. import classNames from 'classnames'
  4. import { useTranslation } from 'react-i18next'
  5. import MaterialIcon from '@/shared/components/material-icon'
  6. const sizeClasses = new Map<ButtonProps['size'], string>([
  7. ['small', 'btn-sm'],
  8. ['default', ''],
  9. ['large', 'btn-lg'],
  10. ])
  11. export default function Button({
  12. children,
  13. className,
  14. leadingIcon,
  15. isLoading = false,
  16. size = 'default',
  17. trailingIcon,
  18. variant = 'primary',
  19. ...props
  20. }: ButtonProps) {
  21. const { t } = useTranslation()
  22. const sizeClass = sizeClasses.get(size)
  23. const buttonClassName = classNames('d-inline-grid', sizeClass, className, {
  24. 'button-loading': isLoading,
  25. })
  26. const loadingSpinnerClassName =
  27. size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small'
  28. const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
  29. return (
  30. <BS5Button className={buttonClassName} variant={variant} {...props}>
  31. {isLoading && (
  32. <span className="spinner-container">
  33. <Spinner
  34. animation="border"
  35. aria-hidden="true"
  36. as="span"
  37. className={loadingSpinnerClassName}
  38. role="status"
  39. />
  40. <span className="sr-only">{t('loading')}</span>
  41. </span>
  42. )}
  43. <span className="button-content" aria-hidden={isLoading}>
  44. {leadingIcon && (
  45. <MaterialIcon type={leadingIcon} className={materialIconClassName} />
  46. )}
  47. {children}
  48. {trailingIcon && (
  49. <MaterialIcon type={trailingIcon} className={materialIconClassName} />
  50. )}
  51. </span>
  52. </BS5Button>
  53. )
  54. }