button.tsx 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  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. loadingLabel,
  17. size = 'default',
  18. trailingIcon,
  19. variant = 'primary',
  20. ...props
  21. }: ButtonProps) {
  22. const { t } = useTranslation()
  23. const sizeClass = sizeClasses.get(size)
  24. const buttonClassName = classNames('d-inline-grid', sizeClass, className, {
  25. 'button-loading': isLoading,
  26. })
  27. const loadingSpinnerClassName =
  28. size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small'
  29. const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
  30. return (
  31. <BS5Button className={buttonClassName} variant={variant} {...props}>
  32. {isLoading && (
  33. <span className="spinner-container">
  34. <Spinner
  35. animation="border"
  36. aria-hidden="true"
  37. as="span"
  38. className={loadingSpinnerClassName}
  39. role="status"
  40. />
  41. <span className="visually-hidden">
  42. {loadingLabel ?? t('loading')}
  43. </span>
  44. </span>
  45. )}
  46. <span className="button-content" aria-hidden={isLoading}>
  47. {leadingIcon && (
  48. <MaterialIcon type={leadingIcon} className={materialIconClassName} />
  49. )}
  50. {children}
  51. {trailingIcon && (
  52. <MaterialIcon type={trailingIcon} className={materialIconClassName} />
  53. )}
  54. </span>
  55. </BS5Button>
  56. )
  57. }