notice.tsx 6.4 KB

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