notification.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import classNames from 'classnames'
  2. import React, { ReactElement, useState } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import MaterialIcon from './material-icon'
  5. export type NotificationType = 'info' | 'success' | 'warning' | 'error'
  6. type NotificationProps = {
  7. action?: React.ReactElement
  8. ariaLive?: 'polite' | 'off' | 'assertive'
  9. className?: string
  10. content: React.ReactElement | string
  11. customIcon?: React.ReactElement
  12. isDismissible?: boolean
  13. isActionBelowContent?: boolean
  14. onDismiss?: () => void
  15. title?: string
  16. type: NotificationType
  17. }
  18. function NotificationIcon({
  19. notificationType,
  20. customIcon,
  21. }: {
  22. notificationType: NotificationType
  23. customIcon?: ReactElement
  24. }) {
  25. let icon = <MaterialIcon type="info" />
  26. if (customIcon) {
  27. icon = customIcon
  28. } else if (notificationType === 'success') {
  29. icon = <MaterialIcon type="check_circle" />
  30. } else if (notificationType === 'warning') {
  31. icon = <MaterialIcon type="warning" />
  32. } else if (notificationType === 'error') {
  33. icon = <MaterialIcon type="error" />
  34. }
  35. return <div className="notification-icon">{icon}</div>
  36. }
  37. function Notification({
  38. action,
  39. ariaLive,
  40. className = '',
  41. content,
  42. customIcon,
  43. isActionBelowContent,
  44. isDismissible,
  45. onDismiss,
  46. title,
  47. type,
  48. }: NotificationProps) {
  49. type = type || 'info'
  50. const { t } = useTranslation()
  51. const [show, setShow] = useState(true)
  52. const notificationClassName = classNames(
  53. 'notification',
  54. `notification-type-${type}`,
  55. isActionBelowContent ? 'notification-cta-below-content' : '',
  56. className
  57. )
  58. const handleDismiss = () => {
  59. setShow(false)
  60. if (onDismiss) onDismiss()
  61. }
  62. if (!show) {
  63. return null
  64. }
  65. return (
  66. <div
  67. className={notificationClassName}
  68. aria-live={ariaLive || 'off'}
  69. role="alert"
  70. >
  71. <NotificationIcon notificationType={type} customIcon={customIcon} />
  72. <div className="notification-content-and-cta">
  73. <div className="notification-content">
  74. {title && (
  75. <p>
  76. <b>{title}</b>
  77. </p>
  78. )}
  79. {content}
  80. </div>
  81. {action && <div className="notification-cta">{action}</div>}
  82. </div>
  83. {isDismissible && (
  84. <div className="notification-close-btn">
  85. <button aria-label={t('close')} onClick={handleDismiss}>
  86. <MaterialIcon type="close" />
  87. </button>
  88. </div>
  89. )}
  90. </div>
  91. )
  92. }
  93. export default Notification