@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
259 lines • 8.54 kB
JavaScript
import React from 'react';
import ReactDOM from 'react-dom';
import cls from 'classnames';
import PropTypes from 'prop-types';
import ConfigContext from '../configProvider/context';
import NotificationListFoundation from '@douyinfe/semi-foundation/lib/es/notification/notificationListFoundation';
import { cssClasses, strings } from '@douyinfe/semi-foundation/lib/es/notification/constants';
import Notice from './notice';
import BaseComponent from '../_base/baseComponent';
import '@douyinfe/semi-foundation/lib/es/notification/notification.css';
import getUuid from '@douyinfe/semi-foundation/lib/es/utils/uuid';
import useNotification from './useNotification';
import CSSAnimation from "../_cssAnimation";
let ref = null;
const defaultConfig = {
duration: 3,
position: 'topRight',
motion: true,
content: '',
title: '',
zIndex: 1010
};
class NotificationList extends BaseComponent {
constructor(props) {
var _this;
super(props);
_this = this;
this.add = noticeOpts => this.foundation.addNotice(noticeOpts);
this.has = id => this.foundation.has(id);
this.remove = id => {
this.foundation.removeNotice(String(id));
};
this.update = (id, opts) => {
return this.foundation.update(id, opts);
};
this.destroyAll = () => this.foundation.destroyAll();
this.renderNoticeInPosition = function (notices, position) {
let removedItems = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
let updatedItems = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
const className = cls(cssClasses.LIST);
// TODO notifyOnClose
if (notices.length) {
const style = _this.setPosInStyle(notices[0]);
return (
/*#__PURE__*/
// @ts-ignore
React.createElement("div", {
placement: position,
key: position,
className: className,
style: style
}, notices.map((notice, index) => {
const isRemoved = removedItems.find(removedItem => removedItem.id === notice.id) !== undefined;
return /*#__PURE__*/React.createElement(CSSAnimation, {
key: notice.id,
animationState: isRemoved ? "leave" : "enter",
startClassName: `${cssClasses.NOTICE}-animation-${isRemoved ? "hide" : "show"}_${position}`
}, _ref => {
let {
animationClassName,
animationEventsNeedBind,
isAnimating
} = _ref;
return isRemoved && !isAnimating ? null : /*#__PURE__*/React.createElement(Notice, Object.assign({}, notice, {
ref: notice => {
if (notice && updatedItems.some(item => item.id === notice.props.id)) {
notice.foundation.restartCloseTimer();
}
},
className: cls({
[notice.className]: Boolean(notice.className),
[animationClassName]: true
})
}, animationEventsNeedBind, {
style: Object.assign({}, notice.style),
close: _this.remove
}));
});
}))
);
}
return null;
};
this.state = {
notices: [],
removedItems: [],
updatedItems: []
};
this.noticeStorage = [];
this.removeItemStorage = [];
this.foundation = new NotificationListFoundation(this.adapter);
}
get adapter() {
var _this2 = this;
return Object.assign(Object.assign({}, super.adapter), {
updateNotices: function (notices) {
let removedItems = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
let updatedItems = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
_this2.noticeStorage = [...notices];
_this2.removeItemStorage = [...removedItems];
// setState is async sometimes and react often merges state, so use "this" , make sure other code always get right data.
_this2.setState({
notices,
removedItems,
updatedItems
});
},
getNotices: () => this.noticeStorage
});
}
static addNotice(notice) {
var _a;
notice = Object.assign(Object.assign({}, defaultConfig), notice);
const id = (_a = notice.id) !== null && _a !== void 0 ? _a : getUuid('notification');
if (!ref) {
const {
getPopupContainer
} = notice;
const div = document.createElement('div');
if (!this.wrapperId) {
this.wrapperId = getUuid('notification-wrapper').slice(0, 32);
}
div.className = cssClasses.WRAPPER;
div.id = this.wrapperId;
div.style.zIndex = String(typeof notice.zIndex === 'number' ? notice.zIndex : defaultConfig.zIndex);
if (getPopupContainer) {
const container = getPopupContainer();
container.appendChild(div);
} else {
document.body.appendChild(div);
}
ReactDOM.render(/*#__PURE__*/React.createElement(NotificationList, {
ref: instance => ref = instance
}), div, () => {
ref.add(Object.assign(Object.assign({}, notice), {
id
}));
});
} else {
if (ref.has(`${id}`)) {
ref.update(id, notice);
} else {
ref.add(Object.assign(Object.assign({}, notice), {
id
}));
}
}
return id;
}
static removeNotice(id) {
if (ref) {
ref.remove(id);
}
return id;
}
static info(opts) {
return this.addNotice(Object.assign(Object.assign({}, opts), {
type: 'info'
}));
}
static success(opts) {
return this.addNotice(Object.assign(Object.assign({}, opts), {
type: 'success'
}));
}
static error(opts) {
return this.addNotice(Object.assign(Object.assign({}, opts), {
type: 'error'
}));
}
static warning(opts) {
return this.addNotice(Object.assign(Object.assign({}, opts), {
type: 'warning'
}));
}
static open(opts) {
return this.addNotice(Object.assign(Object.assign({}, opts), {
type: 'default'
}));
}
static close(id) {
return this.removeNotice(id);
}
static destroyAll() {
if (ref) {
ref.destroyAll();
const wrapper = document.querySelector(`#${this.wrapperId}`);
ReactDOM.unmountComponentAtNode(wrapper);
wrapper && wrapper.parentNode.removeChild(wrapper);
ref = null;
this.wrapperId = null;
}
}
static config(opts) {
['top', 'left', 'bottom', 'right'].map(pos => {
if (pos in opts) {
defaultConfig[pos] = opts[pos];
}
});
if (typeof opts.zIndex === 'number') {
defaultConfig.zIndex = opts.zIndex;
}
if (typeof opts.duration === 'number') {
defaultConfig.duration = opts.duration;
}
if (typeof opts.position === 'string') {
defaultConfig.position = opts.position;
}
}
setPosInStyle(noticeInstance) {
const style = {};
['top', 'left', 'bottom', 'right'].forEach(pos => {
if (pos in noticeInstance) {
const val = noticeInstance[pos];
style[pos] = typeof val === 'number' ? `${val}px` : val;
}
});
return style;
}
render() {
let {
notices
} = this.state;
const {
removedItems,
updatedItems
} = this.state;
notices = Array.from(new Set([...notices, ...removedItems]));
const noticesInPosition = {
top: [],
topLeft: [],
topRight: [],
bottom: [],
bottomLeft: [],
bottomRight: []
};
notices.forEach(notice => {
const direction = notice.direction || this.context.direction;
const defaultPosition = direction === 'rtl' ? 'topLeft' : 'topRight';
const position = notice.position || defaultPosition;
noticesInPosition[position].push(notice);
});
const noticesList = Object.entries(noticesInPosition).map(obj => {
const pos = obj[0];
const noticesInPos = obj[1];
return this.renderNoticeInPosition(noticesInPos, pos, removedItems, updatedItems);
});
return /*#__PURE__*/React.createElement(React.Fragment, null, noticesList);
}
}
NotificationList.contextType = ConfigContext;
NotificationList.propTypes = {
style: PropTypes.object,
className: PropTypes.string,
direction: PropTypes.oneOf(strings.directions)
};
NotificationList.defaultProps = {};
NotificationList.useNotification = useNotification;
export default NotificationList;