notice.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /* eslint-disable no-unreachable */
  2. import React from 'react';
  3. import cls from 'classnames';
  4. import PropTypes from 'prop-types';
  5. import ConfigContext, { ContextValue } from '../configProvider/context';
  6. import { cssClasses, numbers, strings } from '@douyinfe/semi-foundation/notification/constants';
  7. import NotificationFoundation, {
  8. NoticeAdapter,
  9. NoticeProps,
  10. NoticeState
  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';
  16. import { IconAlertCircle, IconAlertTriangle, IconClose, IconInfoCircle, IconTickCircle } from '@douyinfe/semi-icons';
  17. import { getUuidShort } from '@douyinfe/semi-foundation/utils/uuid';
  18. export interface NoticeReactProps extends NoticeProps {
  19. style?: React.CSSProperties;
  20. title?: React.ReactNode;
  21. content?: React.ReactNode;
  22. icon?: React.ReactNode;
  23. onClick?: (e: React.MouseEvent) => void;
  24. onAnimationEnd?: (e: React.AnimationEvent) => void;
  25. onAnimationStart?: (e: React.AnimationEvent) => void;
  26. }
  27. const prefixCls = cssClasses.NOTICE;
  28. const { duration } = numbers;
  29. const { types, themes, directions } = strings;
  30. class Notice extends BaseComponent<NoticeReactProps, NoticeState> {
  31. static contextType = ConfigContext;
  32. static propTypes = {
  33. duration: PropTypes.number,
  34. id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  35. title: PropTypes.node,
  36. content: PropTypes.node, // strings、numbers、array、element
  37. type: PropTypes.oneOf(types),
  38. theme: PropTypes.oneOf(themes),
  39. icon: PropTypes.node,
  40. onClick: PropTypes.func,
  41. onClose: PropTypes.func,
  42. onCloseClick: PropTypes.func,
  43. showClose: PropTypes.bool,
  44. // private props
  45. close: PropTypes.func,
  46. direction: PropTypes.oneOf(directions),
  47. };
  48. static defaultProps = {
  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. // eslint-disable-next-line jsx-a11y/no-static-element-interactions
  148. <div
  149. className={wrapper}
  150. style={style}
  151. onMouseEnter={this.clearCloseTimer}
  152. onMouseLeave={this.startCloseTimer}
  153. onClick={this.notifyClick}
  154. aria-labelledby={titleID}
  155. role={'alert'}
  156. onAnimationEnd={this.props.onAnimationEnd}
  157. onAnimationStart={this.props.onAnimationStart}
  158. >
  159. <div>{this.renderTypeIcon()}</div>
  160. <div className={`${prefixCls}-inner`}>
  161. <div className={`${prefixCls}-content-wrapper`}>
  162. {title ? (
  163. <div id={titleID} className={`${prefixCls}-title`} x-semi-prop="title">
  164. {title}
  165. </div>
  166. ) : (
  167. ''
  168. )}
  169. {content ? (
  170. <div className={`${prefixCls}-content`} x-semi-prop="content">
  171. {content}
  172. </div>
  173. ) : (
  174. ''
  175. )}
  176. </div>
  177. {showClose && (
  178. <Button
  179. className={`${prefixCls}-icon-close`}
  180. type="tertiary"
  181. icon={<IconClose />}
  182. theme="borderless"
  183. size="small"
  184. onClick={this.close}
  185. />
  186. )}
  187. </div>
  188. </div>
  189. );
  190. }
  191. }
  192. export default Notice;