notification.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. iconPlacement?: 'top' | 'center'
  19. disclaimer?: React.ReactElement | string
  20. isDismissible?: boolean
  21. isActionBelowContent?: boolean
  22. onDismiss?: () => void
  23. title?: string
  24. type: NotificationType
  25. id?: string
  26. }
  27. export function NotificationIcon({
  28. notificationType,
  29. customIcon,
  30. iconPlacement,
  31. }: {
  32. notificationType: NotificationType
  33. customIcon?: ReactElement
  34. iconPlacement?: 'top' | 'center'
  35. }) {
  36. let icon = <MaterialIcon type="info" />
  37. if (customIcon) {
  38. icon = customIcon
  39. } else if (notificationType === 'success') {
  40. icon = <MaterialIcon type="check_circle" />
  41. } else if (notificationType === 'warning') {
  42. icon = <MaterialIcon type="warning" />
  43. } else if (notificationType === 'error') {
  44. icon = <MaterialIcon type="error" />
  45. } else if (notificationType === 'offer') {
  46. icon = <MaterialIcon type="campaign" />
  47. }
  48. return (
  49. <div
  50. className={classNames(
  51. 'notification-icon',
  52. iconPlacement ? `notification-icon-${iconPlacement}` : ''
  53. )}
  54. >
  55. {icon}
  56. </div>
  57. )
  58. }
  59. function Notification({
  60. action,
  61. ariaLive,
  62. className = '',
  63. content,
  64. customIcon,
  65. iconPlacement = 'top',
  66. disclaimer,
  67. isActionBelowContent,
  68. isDismissible,
  69. onDismiss,
  70. title,
  71. type,
  72. id,
  73. }: NotificationProps) {
  74. type = type || 'info'
  75. const { t } = useTranslation()
  76. const [show, setShow] = useState(true)
  77. const notificationClassName = classNames(
  78. 'notification',
  79. `notification-type-${type}`,
  80. isActionBelowContent ? 'notification-cta-below-content' : '',
  81. className
  82. )
  83. const handleDismiss = () => {
  84. setShow(false)
  85. if (onDismiss) onDismiss()
  86. }
  87. // return null
  88. if (!show) {
  89. return null
  90. }
  91. return (
  92. <div
  93. className={notificationClassName}
  94. aria-live={ariaLive || 'off'}
  95. role="alert"
  96. id={id}
  97. >
  98. {customIcon !== null && (
  99. <NotificationIcon
  100. notificationType={type}
  101. customIcon={customIcon}
  102. iconPlacement={iconPlacement}
  103. />
  104. )}
  105. <div className="notification-content-and-cta">
  106. <div className="notification-content">
  107. {title && (
  108. <p>
  109. <b>{title}</b>
  110. </p>
  111. )}
  112. {content}
  113. </div>
  114. {action && <div className="notification-cta">{action}</div>}
  115. {disclaimer && (
  116. <div className="notification-disclaimer">{disclaimer}</div>
  117. )}
  118. </div>
  119. {isDismissible && (
  120. <div className="notification-close-btn">
  121. <button aria-label={t('close')} onClick={handleDismiss}>
  122. <MaterialIcon type="close" />
  123. </button>
  124. </div>
  125. )}
  126. </div>
  127. )
  128. }
  129. export default Notification