You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Notification.js 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // @flow
  2. import Flag from '@atlaskit/flag';
  3. import EditorErrorIcon from '@atlaskit/icon/glyph/editor/error';
  4. import EditorInfoIcon from '@atlaskit/icon/glyph/editor/info';
  5. import EditorSuccessIcon from '@atlaskit/icon/glyph/editor/success';
  6. import EditorWarningIcon from '@atlaskit/icon/glyph/editor/warning';
  7. import PeopleIcon from '@atlaskit/icon/glyph/people';
  8. import PersonIcon from '@atlaskit/icon/glyph/person';
  9. import QuestionsIcon from '@atlaskit/icon/glyph/questions';
  10. import React, { isValidElement } from 'react';
  11. import { translate } from '../../../base/i18n';
  12. import Message from '../../../base/react/components/web/Message';
  13. import { colors } from '../../../base/ui/Tokens';
  14. import { NOTIFICATION_ICON, NOTIFICATION_TYPE } from '../../constants';
  15. import AbstractNotification, {
  16. type Props
  17. } from '../AbstractNotification';
  18. declare var interfaceConfig: Object;
  19. /**
  20. * Secondary colors for notification icons.
  21. *
  22. * @type {{error, info, normal, success, warning}}
  23. */
  24. const ICON_COLOR = {
  25. error: colors.error06,
  26. normal: colors.primary06,
  27. success: colors.success05,
  28. warning: colors.warning05
  29. };
  30. /**
  31. * Implements a React {@link Component} to display a notification.
  32. *
  33. * @augments Component
  34. */
  35. class Notification extends AbstractNotification<Props> {
  36. /**
  37. * Implements React's {@link Component#render()}.
  38. *
  39. * @inheritdoc
  40. * @returns {ReactElement}
  41. */
  42. render() {
  43. const {
  44. hideErrorSupportLink,
  45. t,
  46. title,
  47. titleArguments,
  48. titleKey,
  49. uid
  50. } = this.props;
  51. return (
  52. <Flag
  53. actions = { this._mapAppearanceToButtons(hideErrorSupportLink) }
  54. description = { this._renderDescription() }
  55. icon = { this._mapAppearanceToIcon() }
  56. id = { uid }
  57. testId = { titleKey || this._getDescriptionKey() }
  58. title = { title || t(titleKey, titleArguments) } />
  59. );
  60. }
  61. _getDescription: () => Array<string>;
  62. _getDescriptionKey: () => string;
  63. _onDismissed: () => void;
  64. /**
  65. * Creates a {@code ReactElement} for displaying the contents of the
  66. * notification.
  67. *
  68. * @private
  69. * @returns {ReactElement}
  70. */
  71. _renderDescription() {
  72. const description = this._getDescription();
  73. // Keeping in mind that:
  74. // - Notifications that use the `translateToHtml` function get an element-based description array with one entry
  75. // - Message notifications receive string-based description arrays that might need additional parsing
  76. // We look for ready-to-render elements, and if present, we roll with them
  77. // Otherwise, we use the Message component that accepts a string `text` prop
  78. const shouldRenderHtml = description.length === 1 && isValidElement(description[0]);
  79. // the id is used for testing the UI
  80. return (
  81. <p data-testid = { this._getDescriptionKey() } >
  82. { shouldRenderHtml ? description : <Message text = { description.join(' ') } /> }
  83. </p>
  84. );
  85. }
  86. /**
  87. * Opens the support page.
  88. *
  89. * @returns {void}
  90. * @private
  91. */
  92. _onOpenSupportLink() {
  93. window.open(interfaceConfig.SUPPORT_URL, '_blank', 'noopener');
  94. }
  95. /**
  96. * Creates action button configurations for the notification based on
  97. * notification appearance.
  98. *
  99. * @param {boolean} hideErrorSupportLink - Indicates if the support link
  100. * should be hidden in the error messages.
  101. * @private
  102. * @returns {Object[]}
  103. */
  104. _mapAppearanceToButtons(hideErrorSupportLink) {
  105. switch (this.props.appearance) {
  106. case NOTIFICATION_TYPE.ERROR: {
  107. const buttons = [
  108. {
  109. content: this.props.t('dialog.dismiss'),
  110. onClick: this._onDismissed
  111. }
  112. ];
  113. if (!hideErrorSupportLink && interfaceConfig.SUPPORT_URL) {
  114. buttons.push({
  115. content: this.props.t('dialog.contactSupport'),
  116. onClick: this._onOpenSupportLink
  117. });
  118. }
  119. return buttons;
  120. }
  121. case NOTIFICATION_TYPE.WARNING:
  122. return [
  123. {
  124. content: this.props.t('dialog.Ok'),
  125. onClick: this._onDismissed
  126. }
  127. ];
  128. default:
  129. if (this.props.customActionNameKey?.length && this.props.customActionHandler?.length) {
  130. return this.props.customActionNameKey.map((customAction: string, customActionIndex: number) => {
  131. return {
  132. content: this.props.t(customAction),
  133. onClick: () => {
  134. if (this.props.customActionHandler[customActionIndex]()) {
  135. this._onDismissed();
  136. }
  137. },
  138. testId: customAction
  139. };
  140. });
  141. }
  142. return [];
  143. }
  144. }
  145. /**
  146. * Returns the Icon type component to be used, based on icon or appearance.
  147. *
  148. * @returns {ReactElement}
  149. */
  150. _getIcon() {
  151. let Icon;
  152. switch (this.props.icon || this.props.appearance) {
  153. case NOTIFICATION_ICON.ERROR:
  154. Icon = EditorErrorIcon;
  155. break;
  156. case NOTIFICATION_ICON.WARNING:
  157. Icon = EditorWarningIcon;
  158. break;
  159. case NOTIFICATION_ICON.SUCCESS:
  160. Icon = EditorSuccessIcon;
  161. break;
  162. case NOTIFICATION_ICON.MESSAGE:
  163. Icon = QuestionsIcon;
  164. break;
  165. case NOTIFICATION_ICON.PARTICIPANT:
  166. Icon = PersonIcon;
  167. break;
  168. case NOTIFICATION_ICON.PARTICIPANTS:
  169. Icon = PeopleIcon;
  170. break;
  171. default:
  172. Icon = EditorInfoIcon;
  173. break;
  174. }
  175. return Icon;
  176. }
  177. /**
  178. * Creates an icon component depending on the configured notification
  179. * appearance.
  180. *
  181. * @private
  182. * @returns {ReactElement}
  183. */
  184. _mapAppearanceToIcon() {
  185. const { appearance, icon } = this.props;
  186. const secIconColor = ICON_COLOR[appearance];
  187. const iconSize = 'medium';
  188. const Icon = this._getIcon();
  189. return (<div className = { icon }>
  190. <div className = { `ribbon ${appearance}` } />
  191. <Icon
  192. label = { appearance }
  193. secondaryColor = { secIconColor }
  194. size = { iconSize } />
  195. </div>);
  196. }
  197. }
  198. export default translate(Notification);