button.tsx 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  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 sizeClasses = new Map<ButtonProps['size'], string>([
  8. ['small', 'btn-sm'],
  9. ['default', ''],
  10. ['large', 'btn-lg'],
  11. ])
  12. const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  13. (
  14. {
  15. children,
  16. className,
  17. leadingIcon,
  18. isLoading = false,
  19. loadingLabel,
  20. size = 'default',
  21. trailingIcon,
  22. variant = 'primary',
  23. ...props
  24. },
  25. ref
  26. ) => {
  27. const { t } = useTranslation()
  28. const sizeClass = sizeClasses.get(size)
  29. const buttonClassName = classNames('d-inline-grid', sizeClass, className, {
  30. 'button-loading': isLoading,
  31. })
  32. const loadingSpinnerClassName =
  33. size === 'large' ? 'loading-spinner-large' : 'loading-spinner-small'
  34. const materialIconClassName = size === 'large' ? 'icon-large' : 'icon-small'
  35. return (
  36. <BS5Button
  37. className={buttonClassName}
  38. variant={variant}
  39. {...props}
  40. ref={ref}
  41. >
  42. {isLoading && (
  43. <span className="spinner-container">
  44. <Spinner
  45. animation="border"
  46. aria-hidden="true"
  47. as="span"
  48. className={loadingSpinnerClassName}
  49. role="status"
  50. />
  51. <span className="visually-hidden">
  52. {loadingLabel ?? t('loading')}
  53. </span>
  54. </span>
  55. )}
  56. <span className="button-content" aria-hidden={isLoading}>
  57. {leadingIcon && (
  58. <MaterialIcon
  59. type={leadingIcon}
  60. className={materialIconClassName}
  61. />
  62. )}
  63. {children}
  64. {trailingIcon && (
  65. <MaterialIcon
  66. type={trailingIcon}
  67. className={materialIconClassName}
  68. />
  69. )}
  70. </span>
  71. </BS5Button>
  72. )
  73. }
  74. )
  75. Button.displayName = 'Button'
  76. export default Button