button.tsx 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import { forwardRef } from 'react'
  2. import { Button as BS5Button, Spinner } from 'react-bootstrap-5'
  3. import type { ButtonProps } from '@/features/ui/components/types/button-props'
  4. import classNames from 'classnames'
  5. import { useTranslation } from 'react-i18next'
  6. import MaterialIcon from '@/shared/components/material-icon'
  7. const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  8. (
  9. {
  10. children,
  11. className,
  12. leadingIcon,
  13. isLoading = false,
  14. loadingLabel,
  15. trailingIcon,
  16. variant = 'primary',
  17. ...props
  18. },
  19. ref
  20. ) => {
  21. const { t } = useTranslation()
  22. const buttonClassName = classNames('d-inline-grid', className, {
  23. 'button-loading': isLoading,
  24. })
  25. const loadingSpinnerClassName =
  26. props.size === 'lg' ? 'loading-spinner-large' : 'loading-spinner-small'
  27. const materialIconClassName =
  28. props.size === 'lg' ? 'icon-large' : 'icon-small'
  29. const leadingIconComponent =
  30. leadingIcon && typeof leadingIcon === 'string' ? (
  31. <MaterialIcon type={leadingIcon} className={materialIconClassName} />
  32. ) : (
  33. leadingIcon
  34. )
  35. const trailingIconComponent =
  36. trailingIcon && typeof trailingIcon === 'string' ? (
  37. <MaterialIcon type={trailingIcon} className={materialIconClassName} />
  38. ) : (
  39. trailingIcon
  40. )
  41. return (
  42. <BS5Button
  43. className={buttonClassName}
  44. variant={variant}
  45. {...props}
  46. ref={ref}
  47. >
  48. {isLoading && (
  49. <span className="spinner-container">
  50. <Spinner
  51. animation="border"
  52. aria-hidden="true"
  53. as="span"
  54. className={loadingSpinnerClassName}
  55. role="status"
  56. />
  57. <span className="visually-hidden">
  58. {loadingLabel ?? t('loading')}
  59. </span>
  60. </span>
  61. )}
  62. <span className="button-content" aria-hidden={isLoading}>
  63. {leadingIconComponent}
  64. {children}
  65. {trailingIconComponent}
  66. </span>
  67. </BS5Button>
  68. )
  69. }
  70. )
  71. Button.displayName = 'Button'
  72. export default Button