@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.
407 lines • 14.1 kB
JavaScript
import _isEqual from "lodash/isEqual";
/* eslint-disable jsx-a11y/no-noninteractive-element-to-interactive-role */
import React from 'react';
import ReactDOM from 'react-dom';
import cls from 'classnames';
import PropTypes from 'prop-types';
import { IconClose } from '@douyinfe/semi-icons';
import CalendarFoundation from '@douyinfe/semi-foundation/lib/es/calendar/foundation';
import { cssClasses } from '@douyinfe/semi-foundation/lib/es/calendar/constants';
import LocaleConsumer from '../locale/localeConsumer';
import localeContext from '../locale/context';
import BaseComponent from '../_base/baseComponent';
import Popover from '../popover';
import Button from '../iconButton';
import '@douyinfe/semi-foundation/lib/es/calendar/calendar.css';
const toPercent = num => {
const res = num < 1 ? num * 100 : 100;
return `${res}%`;
};
const prefixCls = `${cssClasses.PREFIX}-month`;
const contentPadding = 60;
const contentHeight = 24;
export default class monthCalendar extends BaseComponent {
constructor(props) {
var _this;
super(props);
_this = this;
this.calcItemLimit = () => {
this.contentCellHeight = this.cellDom.current.getBoundingClientRect().height;
return Math.max(0, Math.ceil((this.contentCellHeight - contentPadding) / contentHeight));
};
this.handleClick = (e, val) => {
const {
onClick
} = this.props;
const value = this.foundation.formatCbValue(val);
onClick && onClick(e, value);
};
this.showCard = (e, key) => {
this.foundation.showCard(e, key);
};
this.renderHeader = dateFnsLocale => {
const {
markWeekend,
displayValue
} = this.props;
this.monthlyData = this.foundation.getMonthlyData(displayValue, dateFnsLocale);
return /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-header`,
role: "presentation"
}, /*#__PURE__*/React.createElement("div", {
role: "presentation",
className: `${prefixCls}-grid`
}, /*#__PURE__*/React.createElement("ul", {
role: "row",
className: `${prefixCls}-grid-row`
}, this.monthlyData[0].map(day => {
const {
weekday
} = day;
const listCls = cls({
[`${cssClasses.PREFIX}-weekend`]: markWeekend && day.isWeekend
});
return /*#__PURE__*/React.createElement("li", {
role: "columnheader",
"aria-label": weekday,
key: `${weekday}-monthheader`,
className: listCls
}, /*#__PURE__*/React.createElement("span", null, weekday));
}))));
};
this.renderEvents = events => {
const {
itemLimit
} = this.state;
if (!events) {
return undefined;
}
const list = events.map((event, ind) => {
const {
leftPos,
width,
topInd,
key,
children
} = event;
const style = {
left: toPercent(leftPos),
width: toPercent(width),
top: `${topInd}em`
};
if (topInd < itemLimit) return /*#__PURE__*/React.createElement("li", {
className: `${cssClasses.PREFIX}-event-item ${cssClasses.PREFIX}-event-month`,
key: key || `${ind}-monthevent`,
style: style
}, children);
return null;
});
return list;
};
this.renderCollapsed = (events, itemInfo, listCls, month) => {
const {
itemLimit,
showCard
} = this.state;
const {
weekday,
dayString,
date
} = itemInfo;
const key = date.toString();
const remained = events.filter(i => Boolean(i)).length - itemLimit;
const cardCls = `${prefixCls}-event-card`;
// const top = contentPadding / 2 + this.state.itemLimit * contentHeight;
const shouldRenderCard = remained > 0;
const closer = /*#__PURE__*/React.createElement(Button, {
className: `${cardCls}-close`,
onClick: e => this.closeCard(e, key),
type: "tertiary",
icon: /*#__PURE__*/React.createElement(IconClose, null),
theme: "borderless",
size: "small"
});
const header = /*#__PURE__*/React.createElement("div", {
className: `${cardCls}-header-info`
}, /*#__PURE__*/React.createElement("div", {
className: `${cardCls}-header-info-weekday`
}, weekday), /*#__PURE__*/React.createElement("div", {
className: `${cardCls}-header-info-date`
}, dayString));
const content = /*#__PURE__*/React.createElement("div", {
className: cardCls
}, /*#__PURE__*/React.createElement("div", {
className: `${cardCls}-content`
}, /*#__PURE__*/React.createElement("div", {
className: `${cardCls}-header`
}, header, closer), /*#__PURE__*/React.createElement("div", {
className: `${cardCls}-body`
}, /*#__PURE__*/React.createElement("ul", {
className: `${cardCls}-list`
}, events.map(item => (/*#__PURE__*/React.createElement("li", {
key: item.key || `${item.start.toString()}-event`
}, item.children)))))));
const pos = showCard && showCard[key] ? showCard[key][1] : 'leftTopOver';
const text = /*#__PURE__*/React.createElement(LocaleConsumer, {
componentName: "Calendar"
}, locale => (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
React.createElement("div", {
className: `${cardCls}-wrapper`,
style: {
bottom: 0
},
onClick: e => this.showCard(e, key)
}, locale.remaining.replace('${remained}', String(remained)))));
return /*#__PURE__*/React.createElement(Popover, {
key: `${date.valueOf()}`,
content: content,
position: pos,
trigger: "custom",
visible: showCard && showCard[key] && showCard[key][0],
ref: ref => this.cardRef.set(key, ref)
}, /*#__PURE__*/React.createElement("li", {
key: date,
className: listCls,
onClick: e => this.handleClick(e, [date])
}, this.formatDayString(date, month, dayString), shouldRenderCard ? text : null, this.renderCusDateGrid(date)));
};
this.formatDayString = (dateObj, month, date) => {
const {
renderDateDisplay
} = this.props;
if (renderDateDisplay) {
return renderDateDisplay(dateObj);
}
if (date === '1') {
return /*#__PURE__*/React.createElement(LocaleConsumer, {
componentName: "Calendar"
}, (locale, localeCode) => (/*#__PURE__*/React.createElement("span", {
className: `${prefixCls}-date`
}, month, /*#__PURE__*/React.createElement("span", {
className: `${cssClasses.PREFIX}-today-date`
}, "\u00A0", date), locale.datestring)));
}
return /*#__PURE__*/React.createElement("span", {
className: `${prefixCls}-date`
}, /*#__PURE__*/React.createElement("span", {
className: `${cssClasses.PREFIX}-today-date`
}, date));
};
this.renderCusDateGrid = date => {
const {
dateGridRender
} = this.props;
if (!dateGridRender) {
return null;
}
return dateGridRender(date.toString(), date);
};
this.renderWeekRow = function (index, weekDay) {
let events = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const {
markWeekend
} = _this.props;
const {
itemLimit
} = _this.state;
const {
display,
day
} = events;
return /*#__PURE__*/React.createElement("div", {
role: "presentation",
className: `${prefixCls}-weekrow`,
ref: _this.cellDom,
key: `${index}-weekrow`
}, /*#__PURE__*/React.createElement("ul", {
role: "row",
className: `${prefixCls}-skeleton`
}, weekDay.map(each => {
const {
date,
dayString,
isToday,
isSameMonth,
isWeekend,
month,
ind
} = each;
const listCls = cls({
[`${cssClasses.PREFIX}-today`]: isToday,
[`${cssClasses.PREFIX}-weekend`]: markWeekend && isWeekend,
[`${prefixCls}-same`]: isSameMonth
});
const shouldRenderCollapsed = Boolean(day && day[ind] && day[ind].length > itemLimit);
const inner = /*#__PURE__*/React.createElement("li", {
role: "gridcell",
"aria-label": date.toLocaleDateString(),
"aria-current": isToday ? "date" : false,
key: `${date}-weeksk`,
className: listCls,
onClick: e => _this.handleClick(e, [date])
}, _this.formatDayString(date, month, dayString), _this.renderCusDateGrid(date));
if (!shouldRenderCollapsed) {
return inner;
}
return _this.renderCollapsed(day[ind], each, listCls, month);
})), /*#__PURE__*/React.createElement("ul", {
className: `${cssClasses.PREFIX}-event-items`
}, display ? _this.renderEvents(display) : null));
};
this.renderMonthGrid = () => {
const {
parsedEvents
} = this.state;
return /*#__PURE__*/React.createElement("div", {
role: "presentation",
className: `${prefixCls}-week`
}, /*#__PURE__*/React.createElement("ul", {
role: "presentation",
className: `${prefixCls}-grid-col`
}, Object.keys(this.monthlyData).map(weekInd => this.renderWeekRow(weekInd, this.monthlyData[weekInd], parsedEvents[weekInd]))));
};
this.state = {
itemLimit: 0,
showCard: {},
parsedEvents: {},
cachedKeys: []
};
this.cellDom = /*#__PURE__*/React.createRef();
this.foundation = new CalendarFoundation(this.adapter);
this.handleClick = this.handleClick.bind(this);
this.cardRef = new Map();
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
registerClickOutsideHandler: (key, cb) => {
const clickOutsideHandler = e => {
const cardInstance = this.cardRef && this.cardRef.get(key);
const cardDom = ReactDOM.findDOMNode(cardInstance);
const target = e.target;
const path = e.composedPath && e.composedPath() || [target];
if (cardDom && !cardDom.contains(target) && !path.includes(cardDom)) {
cb();
}
};
this.clickOutsideHandler = clickOutsideHandler;
document.addEventListener('mousedown', clickOutsideHandler, false);
},
unregisterClickOutsideHandler: () => {
document.removeEventListener('mousedown', this.clickOutsideHandler, false);
},
setMonthlyData: data => {
this.monthlyData = data;
},
getMonthlyData: () => this.monthlyData,
notifyClose: (e, key) => {
const updates = {};
updates[key] = [false];
this.setState(prevState => ({
showCard: Object.assign(Object.assign({}, prevState.showCard), updates)
}));
this.props.onClose && this.props.onClose(e);
},
openCard: (key, spacing) => {
const updates = {};
const pos = spacing ? 'leftTopOver' : 'rightTopOver';
updates[key] = [true, pos];
this.setState(prevState => ({
showCard: Object.assign({}, updates)
}));
},
setParsedEvents: parsedEvents => {
this.setState({
parsedEvents: parsedEvents
});
},
setItemLimit: itemLimit => {
this.setState({
itemLimit
});
},
cacheEventKeys: cachedKeys => {
this.setState({
cachedKeys
});
}
});
}
componentDidMount() {
this.foundation.init();
const itemLimit = this.calcItemLimit();
this.foundation.parseMonthlyEvents(itemLimit);
}
componentWillUnmount() {
this.foundation.destroy();
}
componentDidUpdate(prevProps, prevState) {
const prevEventKeys = prevState.cachedKeys;
const nowEventKeys = this.props.events.map(event => event.key);
let itemLimitUpdate = false;
let {
itemLimit
} = this.state;
if (prevProps.height !== this.props.height) {
itemLimit = this.calcItemLimit();
if (prevState.itemLimit !== itemLimit) {
itemLimitUpdate = true;
}
}
if (!_isEqual(prevEventKeys, nowEventKeys) || itemLimitUpdate || !_isEqual(prevProps.displayValue, this.props.displayValue)) {
this.foundation.parseMonthlyEvents(itemLimit);
}
}
closeCard(e, key) {
this.foundation.closeCard(e, key);
}
render() {
const {
className,
height,
width,
style,
header
} = this.props;
const monthCls = cls(prefixCls, className);
const monthStyle = Object.assign({
height,
width
}, style);
return /*#__PURE__*/React.createElement(LocaleConsumer, {
componentName: "Calendar"
}, (locale, localeCode, dateFnsLocale) => (/*#__PURE__*/React.createElement("div", Object.assign({
role: "grid",
className: monthCls,
key: this.state.itemLimit,
style: monthStyle
}, this.getDataAttr(this.props)), /*#__PURE__*/React.createElement("div", {
role: "presentation",
className: `${prefixCls}-sticky-top`
}, header, this.renderHeader(dateFnsLocale)), /*#__PURE__*/React.createElement("div", {
role: "presentation",
className: `${prefixCls}-grid-wrapper`
}, this.renderMonthGrid()))));
}
}
monthCalendar.propTypes = {
displayValue: PropTypes.instanceOf(Date),
header: PropTypes.node,
events: PropTypes.array,
mode: PropTypes.string,
markWeekend: PropTypes.bool,
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
style: PropTypes.object,
className: PropTypes.string,
dateGridRender: PropTypes.func,
onClick: PropTypes.func,
onClose: PropTypes.func
};
monthCalendar.defaultProps = {
displayValue: new Date(),
events: [],
mode: 'month'
};
monthCalendar.contextType = localeContext;