UNPKG

@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
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;