notice.tsx 5.7 KB

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