notice.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import React from 'react';
  2. import cls from 'classnames';
  3. import PropTypes from 'prop-types';
  4. import ConfigContext, { ContextValue } from '../configProvider/context';
  5. import { cssClasses, numbers, strings } from '@douyinfe/semi-foundation/notification/constants';
  6. import NotificationFoundation, {
  7. NoticeAdapter,
  8. NoticeProps,
  9. NoticeState
  10. } from '@douyinfe/semi-foundation/notification/notificationFoundation';
  11. import Button from '../iconButton';
  12. import BaseComponent from '../_base/baseComponent';
  13. import { getDefaultPropsFromGlobalConfig, isSemiIcon } from '../_utils';
  14. import { noop } from 'lodash';
  15. import { IconAlertCircle, IconAlertTriangle, IconClose, IconInfoCircle, IconTickCircle } from '@douyinfe/semi-icons';
  16. import { getUuidShort } from '@douyinfe/semi-foundation/utils/uuid';
  17. export interface NoticeReactProps extends NoticeProps {
  18. style?: React.CSSProperties;
  19. title?: React.ReactNode;
  20. content?: React.ReactNode;
  21. icon?: React.ReactNode;
  22. onClick?: (e: React.MouseEvent) => void;
  23. onAnimationEnd?: (e: React.AnimationEvent) => void;
  24. onAnimationStart?: (e: React.AnimationEvent) => void
  25. }
  26. const prefixCls = cssClasses.NOTICE;
  27. const { duration } = numbers;
  28. const { types, themes, directions } = strings;
  29. class Notice extends BaseComponent<NoticeReactProps, NoticeState> {
  30. static contextType = ConfigContext;
  31. static propTypes = {
  32. duration: PropTypes.number,
  33. id: PropTypes.string,
  34. title: PropTypes.node,
  35. content: PropTypes.node, // strings、numbers、array、element
  36. type: PropTypes.oneOf(types),
  37. theme: PropTypes.oneOf(themes),
  38. icon: PropTypes.node,
  39. onClick: PropTypes.func,
  40. onClose: PropTypes.func,
  41. onCloseClick: PropTypes.func,
  42. showClose: PropTypes.bool,
  43. // private props
  44. close: PropTypes.func,
  45. direction: PropTypes.oneOf(directions),
  46. };
  47. static __SemiComponentName__ = "Notification";
  48. static defaultProps = getDefaultPropsFromGlobalConfig(Notice.__SemiComponentName__, {
  49. duration,
  50. id: '',
  51. close: noop,
  52. onClose: noop,
  53. onClick: noop,
  54. onCloseClick: noop,
  55. content: '',
  56. title: '',
  57. showClose: true,
  58. theme: 'normal',
  59. });
  60. get adapter(): NoticeAdapter {
  61. return {
  62. ...super.adapter,
  63. notifyWrapperToRemove: (id: string) => {
  64. this.props.close(id);
  65. },
  66. notifyClose: () => {
  67. this.props.onClose();
  68. this.props.onHookClose && this.props.onHookClose();
  69. },
  70. };
  71. }
  72. constructor(props: NoticeReactProps) {
  73. super(props);
  74. this.state = {
  75. visible: true,
  76. };
  77. this.foundation = new NotificationFoundation(this.adapter);
  78. }
  79. context: ContextValue;
  80. componentWillUnmount() {
  81. this.foundation.destroy();
  82. }
  83. renderTypeIcon() {
  84. const { type, icon } = this.props;
  85. const iconMap = {
  86. warning: <IconAlertTriangle size="large"/>,
  87. success: <IconTickCircle size="large"/>,
  88. info: <IconInfoCircle size="large"/>,
  89. error: <IconAlertCircle size="large"/>,
  90. };
  91. let iconType = iconMap[type];
  92. const iconCls = cls({
  93. [`${prefixCls}-icon`]: true,
  94. [`${prefixCls}-${type}`]: true,
  95. });
  96. if (icon) {
  97. iconType = icon;
  98. }
  99. if (iconType) {
  100. return (
  101. <div className={iconCls} x-semi-prop="icon">
  102. {isSemiIcon(iconType) ? React.cloneElement(iconType, { size: iconType.props.size || 'large' }) : iconType}
  103. </div>
  104. );
  105. }
  106. return null;
  107. }
  108. clearCloseTimer = () => {
  109. this.foundation._clearCloseTimer();
  110. };
  111. startCloseTimer = () => {
  112. this.foundation._startCloseTimer();
  113. };
  114. close = (e: React.MouseEvent) => {
  115. this.props.onCloseClick(this.props.id);
  116. this.foundation.close(e);
  117. };
  118. notifyClick = (e: React.MouseEvent) => {
  119. this.props.onClick(e);
  120. };
  121. render() {
  122. const direction = this.props.direction || this.context.direction;
  123. const defaultPosition = direction === 'rtl' ? 'topLeft' : 'topRight';
  124. const {
  125. content,
  126. title,
  127. theme,
  128. position = defaultPosition,
  129. type,
  130. id,
  131. onCloseClick,
  132. className,
  133. showClose,
  134. style,
  135. ...attr
  136. } = this.props;
  137. const { visible } = this.state;
  138. const wrapper = cls(prefixCls, className, {
  139. [`${prefixCls}-close`]: !visible,
  140. [`${prefixCls}-icon-show`]: types.includes(type),
  141. [`${prefixCls}-${type}`]: true,
  142. [`${prefixCls}-${theme}`]: theme === 'light',
  143. [`${prefixCls}-rtl`]: direction === 'rtl',
  144. });
  145. const titleID = getUuidShort({});
  146. return (
  147. <div
  148. className={wrapper}
  149. style={style}
  150. onMouseEnter={this.clearCloseTimer}
  151. onMouseLeave={this.startCloseTimer}
  152. onClick={this.notifyClick}
  153. aria-labelledby={titleID}
  154. role={'alert'}
  155. onAnimationEnd={this.props.onAnimationEnd}
  156. onAnimationStart={this.props.onAnimationStart}
  157. >
  158. <div>{this.renderTypeIcon()}</div>
  159. <div className={`${prefixCls}-inner`}>
  160. <div className={`${prefixCls}-content-wrapper`}>
  161. {title ? (
  162. <div id={titleID} className={`${prefixCls}-title`} x-semi-prop="title">
  163. {title}
  164. </div>
  165. ) : (
  166. ''
  167. )}
  168. {content ? (
  169. <div className={`${prefixCls}-content`} x-semi-prop="content">
  170. {content}
  171. </div>
  172. ) : (
  173. ''
  174. )}
  175. </div>
  176. {showClose && (
  177. <Button
  178. className={`${prefixCls}-icon-close`}
  179. type="tertiary"
  180. icon={<IconClose />}
  181. theme="borderless"
  182. size="small"
  183. onClick={this.close}
  184. />
  185. )}
  186. </div>
  187. </div>
  188. );
  189. }
  190. }
  191. export default Notice;