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.

436 lines 14.7 kB
import _isFunction from "lodash/isFunction"; import _stubFalse from "lodash/stubFalse"; import _noop from "lodash/noop"; /* eslint-disable jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions */ import React from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import MonthFoundation from '@douyinfe/semi-foundation/lib/es/datePicker/monthFoundation'; import { cssClasses, numbers } from '@douyinfe/semi-foundation/lib/es/datePicker/constants'; import BaseComponent from '../_base/baseComponent'; import { isBefore, isAfter, isBetween, isSameDay } from '@douyinfe/semi-foundation/lib/es/datePicker/_utils/index'; import { parseISO } from 'date-fns'; const prefixCls = cssClasses.PREFIX; export default class Month extends BaseComponent { constructor(props) { super(props); this.state = { weekdays: [], month: { weeks: [], monthText: '' }, todayText: '', weeksRowNum: props.weeksRowNum }; this.monthRef = /*#__PURE__*/React.createRef(); } get adapter() { return Object.assign(Object.assign({}, super.adapter), { updateToday: todayText => this.setState({ todayText }), setWeekDays: weekdays => this.setState({ weekdays }), setWeeksRowNum: (weeksRowNum, callback) => this.setState({ weeksRowNum }, callback), updateMonthTable: month => this.setState({ month }), notifyDayClick: day => this.props.onDayClick(day), notifyDayHover: day => this.props.onDayHover(day), notifyWeeksRowNumChange: weeksRowNum => this.props.onWeeksRowNumChange(weeksRowNum) }); } componentDidMount() { this.foundation = new MonthFoundation(this.adapter); this.foundation.init(); } componentWillUnmount() { this.foundation.destroy(); } componentDidUpdate(prevProps, prevState) { if (prevProps.month !== this.props.month) { this.foundation.getMonthTable(); } } getSingleDayStatus(options) { const { rangeInputFocus } = this.props; const { fullDate, todayText, selected, disabledDate, rangeStart, rangeEnd } = options; const disabledOptions = { rangeStart, rangeEnd, rangeInputFocus }; const isToday = fullDate === todayText; const isSelected = selected.has(fullDate); let isDisabled = disabledDate && disabledDate(parseISO(fullDate), disabledOptions); if (!isDisabled && this.props.rangeInputFocus === 'rangeStart' && rangeEnd && this.props.focusRecordsRef && this.props.focusRecordsRef.current.rangeEnd) { // The reason for splitting is that the dateRangeTime format: 'yyyy-MM-dd HH:MM:SS' isDisabled = isAfter(fullDate, rangeEnd.trim().split(/\s+/)[0]); } if (!isDisabled && this.props.rangeInputFocus === 'rangeEnd' && rangeStart && this.props.focusRecordsRef && this.props.focusRecordsRef.current.rangeStart) { // The reason for splitting is that the dateRangeTime format: 'yyyy-MM-dd HH:MM:SS' isDisabled = isBefore(fullDate, rangeStart.trim().split(/\s+/)[0]); } return { isToday, isSelected, isDisabled // Disabled }; } getDateRangeStatus(options) { const { rangeStart, rangeEnd, fullDate, hoverDay, offsetRangeStart, offsetRangeEnd, rangeInputFocus } = options; // If no item is selected, return the empty object directly const _isDateRangeAnySelected = Boolean(rangeStart || rangeEnd); const _isDateRangeSelected = Boolean(rangeStart && rangeEnd); const _isOffsetDateRangeAnyExist = offsetRangeStart || offsetRangeEnd; if (!_isDateRangeAnySelected) { return {}; } // The range selects the hover date, and the normal hover is .semi-datepicker-main: hover const _isHoverDay = isSameDay(hoverDay, fullDate); // When one is selected let _isHoverAfterStart, _isHoverBeforeEnd, isSelectedStart, isSelectedEnd, isHoverDayAroundOneSelected; if (rangeStart) { isSelectedStart = isSameDay(fullDate, rangeStart); if (rangeInputFocus === 'rangeEnd') { _isHoverAfterStart = isBetween(fullDate, { start: rangeStart, end: hoverDay }); } } if (rangeEnd) { isSelectedEnd = isSameDay(fullDate, rangeEnd); if (rangeInputFocus === 'rangeStart') { _isHoverBeforeEnd = isBetween(fullDate, { start: hoverDay, end: rangeEnd }); } } if (!_isDateRangeSelected && _isDateRangeAnySelected) { isHoverDayAroundOneSelected = _isHoverDay; } let isHover; if (!_isOffsetDateRangeAnyExist) { isHover = _isHoverAfterStart || _isHoverBeforeEnd || _isHoverDay; } // Select all let isInRange, isSelectedStartAfterHover, isSelectedEndBeforeHover, isHoverDayInStartSelection, isHoverDayInEndSelection, isHoverDayInRange; if (_isDateRangeSelected) { isInRange = isBetween(fullDate, { start: rangeStart, end: rangeEnd }); if (!_isOffsetDateRangeAnyExist) { isSelectedStartAfterHover = isSelectedStart && isAfter(rangeStart, hoverDay); isSelectedEndBeforeHover = isSelectedEnd && isBefore(rangeEnd, hoverDay); isHoverDayInStartSelection = _isHoverDay && rangeInputFocus === 'rangeStart'; isHoverDayInEndSelection = _isHoverDay && rangeInputFocus === 'rangeEnd'; isHoverDayInRange = _isHoverDay && isBetween(hoverDay, { start: rangeStart, end: rangeEnd }); } } return { isHoverDay: _isHoverDay, isSelectedStart, isSelectedEnd, isInRange, isHover, isSelectedStartAfterHover, isSelectedEndBeforeHover, isHoverDayInRange, isHoverDayInStartSelection, isHoverDayInEndSelection, isHoverDayAroundOneSelected // Hover date and select a date }; } getOffsetDateStatus(options) { const { offsetRangeStart, offsetRangeEnd, rangeStart, rangeEnd, fullDate, hoverDay } = options; // When there is no offset, return the empty object directly const _isOffsetDateRangeNull = !(offsetRangeStart || offsetRangeEnd); if (_isOffsetDateRangeNull) { return {}; } // Range Select base date const _isInRange = isBetween(fullDate, { start: rangeStart, end: rangeEnd }); const _isHoverDay = isSameDay(hoverDay, fullDate); const _isSelectedStart = rangeStart && isSameDay(fullDate, rangeStart); const _isSelectedEnd = rangeEnd && isSameDay(fullDate, rangeEnd); const _isDateRangeSelected = Boolean(rangeStart && rangeEnd); // Determine whether it is offsetStart or offsetRangeEnd const isOffsetRangeStart = isSameDay(fullDate, offsetRangeStart); const isOffsetRangeEnd = isSameDay(fullDate, offsetRangeEnd); const isHoverDayOffset = _isHoverDay; // When selected let isHoverInOffsetRange, isInOffsetRange; if (_isDateRangeSelected) { isHoverInOffsetRange = _isInRange && _isHoverDay; } // When there is an offset area const _isOffsetDateRangeSelected = Boolean(offsetRangeStart && offsetRangeEnd); if (_isOffsetDateRangeSelected) { isInOffsetRange = _isSelectedStart || isBetween(fullDate, { start: offsetRangeStart, end: offsetRangeEnd }) || _isSelectedEnd; } return { isOffsetRangeStart, isOffsetRangeEnd, isHoverInOffsetRange, isHoverDayOffset, isInOffsetRange // Include start and end within the week selection (start and end styles are the same as other dates, so start and end are included) }; } /** * get day current status * @param {Object} fullDate * @param {Object} options * @returns {Object} */ getDayStatus(currentDay, options) { const { fullDate } = currentDay; const { hoverDay, rangeStart, rangeEnd, todayText, offsetRangeStart, offsetRangeEnd, disabledDate, selected, rangeInputFocus } = options; const singleDayStatus = this.getSingleDayStatus({ fullDate, todayText, hoverDay, selected, disabledDate, rangeStart, rangeEnd }); const dateRangeStatus = this.getDateRangeStatus(Object.assign({ fullDate, rangeStart, rangeEnd, hoverDay, offsetRangeStart, offsetRangeEnd, rangeInputFocus }, singleDayStatus)); const offsetDataStatus = this.getOffsetDateStatus(Object.assign(Object.assign({ offsetRangeStart, offsetRangeEnd, rangeStart, rangeEnd, fullDate, hoverDay }, singleDayStatus), dateRangeStatus)); // this parameter will pass to the user when given renderFullDate function, do not delete or modify its key const dayStatus = Object.assign(Object.assign(Object.assign({}, singleDayStatus), dateRangeStatus), offsetDataStatus); return dayStatus; } renderDayOfWeek() { const { locale } = this.props; const weekdayCls = classNames(cssClasses.WEEKDAY); const weekdayItemCls = classNames(`${prefixCls}-weekday-item`); const { weekdays } = this.state; // i18n const weekdaysText = weekdays.map(key => locale.weeks[key]); return /*#__PURE__*/React.createElement("div", { role: "row", className: weekdayCls }, weekdaysText.map((E, i) => (/*#__PURE__*/React.createElement("div", { role: "columnheader", key: E + i, className: weekdayItemCls }, E)))); } renderWeeks() { const { month } = this.state; const { weeks } = month; const { weeksRowNum } = this.props; let style = {}; if (weeksRowNum) { const height = weeksRowNum * numbers.WEEK_HEIGHT; style = { height }; } const weeksCls = classNames(cssClasses.WEEKS); return /*#__PURE__*/React.createElement("div", { className: weeksCls, style: style }, weeks.map((week, weekIndex) => this.renderWeek(week, weekIndex))); } renderWeek(week, weekIndex) { const weekCls = cssClasses.WEEK; return /*#__PURE__*/React.createElement("div", { role: "row", className: weekCls, key: weekIndex }, week.map((day, dayIndex) => this.renderDay(day, dayIndex))); } renderDay(day, dayIndex) { const { todayText } = this.state; const { renderFullDate, renderDate } = this.props; const { fullDate, dayNumber } = day; if (!fullDate) { return /*#__PURE__*/React.createElement("div", { role: "gridcell", tabIndex: -1, key: dayNumber + dayIndex, className: cssClasses.DAY }, /*#__PURE__*/React.createElement("span", null)); } const dayStatus = this.getDayStatus(day, Object.assign({ todayText }, this.props)); const dayCls = classNames(cssClasses.DAY, { [cssClasses.DAY_TODAY]: dayStatus.isToday, [cssClasses.DAY_IN_RANGE]: dayStatus.isInRange, [cssClasses.DAY_HOVER]: dayStatus.isHover, [cssClasses.DAY_SELECTED]: dayStatus.isSelected, [cssClasses.DAY_SELECTED_START]: dayStatus.isSelectedStart, [cssClasses.DAY_SELECTED_END]: dayStatus.isSelectedEnd, [cssClasses.DAY_DISABLED]: dayStatus.isDisabled, // offsetDate class [cssClasses.DAY_HOVER_DAY]: dayStatus.isHoverDayOffset, [cssClasses.DAY_IN_OFFSET_RANGE]: dayStatus.isInOffsetRange, [cssClasses.DAY_SELECTED_RANGE_HOVER]: dayStatus.isHoverInOffsetRange, [cssClasses.DAY_OFFSET_RANGE_START]: dayStatus.isOffsetRangeStart, [cssClasses.DAY_OFFSET_RANGE_END]: dayStatus.isOffsetRangeEnd, // range input class [cssClasses.DAY_SELECTED_START_AFTER_HOVER]: dayStatus.isSelectedStartAfterHover, [cssClasses.DAY_SELECTED_END_BEFORE_HOVER]: dayStatus.isSelectedEndBeforeHover, [cssClasses.DAY_HOVER_DAY_BEFORE_RANGE]: dayStatus.isHoverDayInStartSelection, [cssClasses.DAY_HOVER_DAY_AFTER_RANGE]: dayStatus.isHoverDayInEndSelection, [cssClasses.DAY_HOVER_DAY_AROUND_SINGLE_SELECTED]: dayStatus.isHoverDayAroundOneSelected }); const dayMainCls = classNames({ [`${cssClasses.DAY}-main`]: true }); const fullDateArgs = [dayNumber, fullDate, dayStatus]; const customRender = _isFunction(renderFullDate); return /*#__PURE__*/React.createElement("div", { role: "gridcell", tabIndex: dayStatus.isDisabled ? -1 : 0, "aria-disabled": dayStatus.isDisabled, "aria-selected": dayStatus.isSelected, "aria-label": fullDate, className: !customRender ? dayCls : cssClasses.DAY, title: fullDate, key: dayNumber + dayIndex, onClick: e => !dayStatus.isDisabled && this.foundation.handleClick(day), onMouseEnter: () => this.foundation.handleHover(day), onMouseLeave: () => this.foundation.handleHover() }, customRender ? renderFullDate(...fullDateArgs) : (/*#__PURE__*/React.createElement("div", { className: dayMainCls }, _isFunction(renderDate) ? renderDate(dayNumber, fullDate) : /*#__PURE__*/React.createElement("span", null, dayNumber)))); } render() { const { forwardRef, multiple } = this.props; const weekday = this.renderDayOfWeek(); const weeks = this.renderWeeks(); const monthCls = classNames(cssClasses.MONTH); const ref = forwardRef || this.monthRef; return /*#__PURE__*/React.createElement("div", { role: "grid", "aria-multiselectable": multiple, ref: ref, className: monthCls }, weekday, weeks); } } Month.propTypes = { month: PropTypes.object, selected: PropTypes.object, rangeStart: PropTypes.string, rangeEnd: PropTypes.string, offsetRangeStart: PropTypes.string, offsetRangeEnd: PropTypes.string, onDayClick: PropTypes.func, onDayHover: PropTypes.func, weekStartsOn: PropTypes.number, disabledDate: PropTypes.func, weeksRowNum: PropTypes.number, onWeeksRowNumChange: PropTypes.func, renderDate: PropTypes.func, renderFullDate: PropTypes.func, hoverDay: PropTypes.string, startDateOffset: PropTypes.func, endDateOffset: PropTypes.func, rangeInputFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), focusRecordsRef: PropTypes.object, multiple: PropTypes.bool }; Month.defaultProps = { month: new Date(), selected: new Set(), rangeStart: '', rangeEnd: '', onDayClick: _noop, onDayHover: _noop, onWeeksRowNumChange: _noop, weekStartsOn: numbers.WEEK_START_ON, disabledDate: _stubFalse, weeksRowNum: 0 };