notification.tsx 2.9 KB

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