UNPKG

@zohodesk/components

Version:

Dot UI is a customizable React component library built to deliver a clean, accessible, and developer-friendly UI experience. It offers a growing set of reusable components designed to align with modern design systems and streamline application development

777 lines (709 loc) • 20.4 kB
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /* eslint css-modules/no-unused-class: [0, { markAsUsed: ['datesStr', 'dateContainer', 'dateRow', 'today', 'emptySpan', 'dropDown'] }] */ /* eslint-disable react/forbid-component-props */ import React from 'react'; import { DateTime_propTypes } from "./props/propTypes"; import { DateTime_defaultProps } from "./props/defaultProps"; import datetime from '@zohodesk/datetimejs'; import CalendarView from "./CalendarView"; import YearView from "./YearView"; import DateTimePopupHeader from "./DateTimePopupHeader"; import DateTimePopupFooter from "./DateTimePopupFooter"; import Time from "./Time"; import style from "./DateTime.module.css"; import { formatDate, getMonthEnd } from "../utils/datetime/common"; import { getIsEmptyValue } from "../utils/Common"; import { monthNamesDefault, monthNamesShortDefault, dayNamesDefault, dayNamesShortDefault, ampmTextDefault } from "./constants"; import ResponsiveDropBox from "../ResponsiveDropBox/ResponsiveDropBox"; import { Box } from "../Layout"; import { getHourSuggestions, getMinuteSuggestions, addZeroIfNeeded } from "./dateFormatUtils"; import { getDateText } from "./dateFormatUtils/dateFormat"; function title(date, year, month, monthNames = []) { const HeadingText = `${monthNames[month] || ''} ${year}`; return HeadingText; } export default class DateTime extends React.PureComponent { constructor(props) { super(props); this.getHours = this.getHours.bind(this); this.amPmSelect = this.amPmSelect.bind(this); this.hoursSelect = this.hoursSelect.bind(this); this.dateSelect = this.dateSelect.bind(this); this.handleClear = this.handleClear.bind(this); this.handleSelect = this.handleSelect.bind(this); this.minutesSelect = this.minutesSelect.bind(this); this.modifyCalendar = this.modifyCalendar.bind(this); this.handleGetSelectedDate = this.handleGetSelectedDate.bind(this); this.handleChange = this.handleChange.bind(this); this.handleCalendarNavigation = this.handleCalendarNavigation.bind(this); this.handleExposeMethods = this.handleExposeMethods.bind(this); this.handleDateReset = this.handleDateReset.bind(this); this.handleOpenYearView = this.handleOpenYearView.bind(this); this.handleSelectMonth = this.handleSelectMonth.bind(this); this.handleSelectYear = this.handleSelectYear.bind(this); this.handleSelectMonthViaYearView = this.handleSelectMonthViaYearView.bind(this); this.handleGetStateValues = this.handleGetStateValues.bind(this); this.handleYearViewToggle = this.handleYearViewToggle.bind(this); const { ampmText = ampmTextDefault } = props.i18nKeys; this.ampmSuggestions = (() => { const ampmSuggestions = []; ampmText.forEach((text, index) => { if (index === 0) { ampmSuggestions.push({ text, id: 'AM' }); } else if (index === 1) { ampmSuggestions.push({ text, id: 'PM' }); } }); return ampmSuggestions; })(); const initalStateObj = this.getStateFromProps(props); this.state = Object.assign({}, initalStateObj, { isYearView: false, isMonthOpen: false }); } componentDidMount() { this.handleExposeMethods(true); } componentDidUpdate(prevProps) { const { value, isActive, is24Hour } = this.props; if (prevProps.value !== value || is24Hour !== prevProps.is24Hour) { this.setState(this.getStateFromProps(this.props)); } if (prevProps.isActive !== isActive && !isActive) { this.setState({ isYearView: false, isMonthOpen: false }); } } componentWillUnmount() { this.handleExposeMethods(false); } getStateFromProps(props) { let date, month, year, hours, mins, amPm; let { value, timeZone, defaultTime, needDefaultTime, isDateTimeField, is24Hour } = props; //defaultTime --> 12:00:PM defaultTime = needDefaultTime ? defaultTime ? defaultTime : '12:00:PM' : ''; let defaultHour, defaultMin, defaultAmPm; let todayObj = new Date(); let todayDate = todayObj.getDate(); let todayMonth = todayObj.getMonth(); let todayYear = todayObj.getFullYear(); if (!value) { [defaultHour, defaultMin, defaultAmPm] = defaultTime ? defaultTime.split(':') : []; defaultHour = parseInt(defaultHour); defaultMin = parseInt(defaultMin); } const dateObj = getDateText(value, isDateTimeField, timeZone); date = dateObj.getDate(); month = dateObj.getMonth(); year = dateObj.getFullYear(); hours = defaultHour ? defaultHour : dateObj.getHours(); mins = !getIsEmptyValue(defaultMin) ? defaultMin : dateObj.getMinutes(); mins = addZeroIfNeeded(mins); amPm = defaultAmPm ? defaultAmPm : hours < 12 ? 'AM' : 'PM'; hours = this.getHours(hours, is24Hour); return { date, month, year, mins, hours, amPm, todayDate, todayMonth, todayYear }; } getHours(hoursParam, is24Hour) { let hours = hoursParam; if (!is24Hour) { if (hours === 0) { hours = 12; } else if (hours > 12) { hours -= 12; } } hours = addZeroIfNeeded(hours); return hours; } handleGetSelectedDate(selectedInfo = {}) { const { min, max, timeZone, isDateTimeField, dateFormat, is24Hour, customDateFormat } = this.props; let { year, month, date, hours, mins, amPm } = selectedInfo; if (!is24Hour) { if (parseInt(hours) === 12) { hours = amPm === 'AM' ? 0 : 12; } if (amPm === 'PM') { if (hours < 12) { hours = parseInt(hours) + 12; } } } let minInMillis = min ? datetime.millis(min) : null, maxInMillis = max ? datetime.millis(max) : null, selectedInMillis, selectedValue = '', formattedValue; let currentDate = new Date(); let sec = currentDate.getSeconds(); let milliSec = currentDate.getMilliseconds(); if (isDateTimeField || customDateFormat) { const dateArgs = [year, month, date, hours, mins]; if (customDateFormat) { dateArgs.push(sec, milliSec); } selectedInMillis = timeZone ? datetime.tz.tzToUtc(Date.UTC(...dateArgs), timeZone) : Date.UTC(...dateArgs); selectedValue = datetime.ISO(selectedInMillis); formattedValue = formatDate(new Date(...dateArgs), customDateFormat == null ? is24Hour ? `${dateFormat} HH:mm:ss` : `${dateFormat} hh:mm:ss A` : `${customDateFormat}`); } else { selectedInMillis = Date.UTC(year, month, date); selectedValue = formatDate(new Date(year, month, date), 'YYYY-MM-DD'); formattedValue = formatDate(new Date(year, month, date), dateFormat); } let isError = false; let errorType = ''; if (minInMillis && minInMillis > selectedInMillis) { isError = true; errorType = 'MIN'; } else if (maxInMillis && maxInMillis < selectedInMillis) { isError = true; errorType = 'MAX'; } return { isError, errorType, selectedValue, formattedValue }; } handleSelect(e) { e && e.preventDefault(); const { onError, onSelect, minErrorText, maxErrorText } = this.props; const { year, month, date, hours, mins, amPm } = this.state; const { isError, errorType, selectedValue, formattedValue } = this.handleGetSelectedDate({ year, month, date, hours, mins, amPm }); if (isError) { if (errorType === 'MIN') { onError && onError(minErrorText, true); } else if (errorType === 'MAX') { onError && onError(maxErrorText, true); } } else { onSelect(selectedValue, formattedValue, e); } } handleChange(selectedInfo = {}) { const { onError, onChange, minErrorText, maxErrorText } = this.props; const { year: oldYear, month: oldMonth, date: oldDate, hours: oldHours, mins: oldMins, amPm: oldAMPM } = this.state; const { year, month, date, hours, mins, amPm } = selectedInfo; const newSelectedInfo = { year: getIsEmptyValue(year) ? oldYear : year, month: getIsEmptyValue(month) ? oldMonth : month, date: getIsEmptyValue(date) ? oldDate : date, hours: getIsEmptyValue(hours) ? oldHours : hours, mins: getIsEmptyValue(mins) ? oldMins : mins, amPm: getIsEmptyValue(amPm) ? oldAMPM : amPm }; if (onChange) { const { isError, errorType, selectedValue, formattedValue } = this.handleGetSelectedDate(newSelectedInfo); if (isError) { if (errorType === 'MIN') { onError && onError(minErrorText, true); } else if (errorType === 'MAX') { onError && onError(maxErrorText, true); } } else { this.setState(newSelectedInfo); onChange(selectedValue, formattedValue); } } else { this.setState(newSelectedInfo); } } handleClear(e) { const { onSelect, onClear } = this.props; if (onClear) { onClear(e); } else { onSelect('', '', e); } } dateSelect(date, month, year, e) { this.handleChange({ date, month, year }); e && e.preventDefault(); const { onError, onDateSelect, minErrorText, maxErrorText } = this.props; const { hours, mins, amPm } = this.state; const { isError, errorType, selectedValue, formattedValue } = this.handleGetSelectedDate({ year, month, date, hours, mins, amPm }); if (isError) { if (errorType === 'MIN') { onError && onError(minErrorText, true); } else if (errorType === 'MAX') { onError && onError(maxErrorText, true); } } else { onDateSelect && onDateSelect(selectedValue, formattedValue, e); } } hoursSelect(hours) { this.handleChange({ hours }); } minutesSelect(mins) { this.handleChange({ mins }); } amPmSelect(amPm) { this.handleChange({ amPm }); } /*global closeGroupPopups*/ closePopup() { closeGroupPopups('calender'); } handleCalendarNavigation(type, selectedInfo) { const { year: stateYear, month: stateMonth, date: stateDate } = selectedInfo; let date = stateDate; let month = stateMonth; const year = stateYear; const getDate = (month, year) => { const monthEnd = getMonthEnd(month, year); return monthEnd >= parseInt(date) ? date : monthEnd; }; const modifyCalendarRecursion = recursionType => { if (recursionType === 'nextYear') { //Click next year icon const newYear = year + 1; return { date: getDate(month, newYear), month, year: newYear }; } else if (recursionType === 'previousYear') { //Click previous year icon const newYear = year - 1; return { date: getDate(month, newYear), month, year: newYear }; } else if (recursionType === 'nextMonth') { //Click next month icon if (month === 11) { month = 0; return modifyCalendarRecursion('nextYear'); } const newMonth = month + 1; return { date: getDate(newMonth, year), month: newMonth, year }; } else if (recursionType === 'previousMonth') { //Click previous month icon if (month === 0) { month = 11; return modifyCalendarRecursion('previousYear'); } const newMonth = month - 1; return { date: getDate(newMonth, year), month: newMonth, year }; } else if (recursionType === 'nextDate') { const monthEnd = getMonthEnd(month, year); if (date === monthEnd) { date = 1; return modifyCalendarRecursion('nextMonth'); } const newDate = parseInt(date) + 1; return { date: newDate, month, year }; } else if (recursionType === 'previousDate') { if (date === 1) { date = month === 0 ? getMonthEnd(11, year - 1) : getMonthEnd(month - 1, year); return modifyCalendarRecursion('previousMonth'); } const newDate = parseInt(date) - 1; return { date: newDate, month, year }; } }; return modifyCalendarRecursion(type); } modifyCalendar(type) { const { year, month, date } = this.state; return this.handleChange(this.handleCalendarNavigation(type, { year, month, date })); } handleDateReset() { this.setState(this.getStateFromProps(this.props)); } handleGetStateValues() { const { isActive } = this.props; const { isYearView, isMonthOpen } = this.state; return { isActive, isYearView, isMonthOpen }; } handleYearViewToggle(isYearOpen, isMonthOpen) { this.setState({ isYearView: isYearOpen, isMonthOpen }); } handleExposeMethods(isMount) { const { getMethods } = this.props; let methods = {}; if (isMount) { methods = { resetLocalDate: this.handleDateReset, getStateValues: this.handleGetStateValues, toggleYearView: this.handleYearViewToggle }; } else { methods = { resetLocalDate: null, getStateValues: null, toggleYearView: null }; } getMethods && getMethods(methods); } handleOpenYearView() { const { isYearView, isMonthOpen } = this.state; this.setState({ isYearView: !isYearView, isMonthOpen: !isMonthOpen }); } handleSelectYear(year) { this.setState({ year, isMonthOpen: true }); } handleSelectMonth(month) { const { date, year } = this.state; const monthEnd = getMonthEnd(month, year); let newDate = date; if (date > monthEnd) { newDate = monthEnd; } this.setState({ month, date: newDate }); } handleSelectMonthViaYearView(month) { this.handleSelectMonth(month); this.setState({ isYearView: false, isMonthOpen: false }); } render() { const { date, month, year, hours, mins, amPm, isYearView, isMonthOpen, todayDate, todayMonth, todayYear } = this.state; const { isDateTimeField, isActive, position, isReady, getRef, onClick, dataId, needResponsive, isAbsolute, isAnimate, needAction, boxSize, className, innerClass, isPadding, i18nKeys, is24Hour, isDefaultPosition, positionsOffset, targetOffset, isRestrictScroll, dropBoxPortalId, renderCustomHeader, renderCustomFooter, customProps = {}, weekStartDay, holidays } = this.props; const { TimeProps = {}, DropBoxProps = {} } = customProps; const { timeText = 'Time', submitText = 'Set', cancelText = 'Clear', hourEmptyText = 'No search results', minuteEmptyText = 'No search results', nextMonthTitleText = 'Next month', prevMonthTitleText = 'Prev month', monthNames = monthNamesDefault, monthNamesShort = monthNamesShortDefault, dayNames = dayNamesDefault, dayNamesShort = dayNamesShortDefault } = i18nKeys; const showmonthtxt = title(date, year, month, monthNames); let customDayNames = dayNames, customDayNamesShort = dayNamesShort, customizedHolidays = holidays; if (weekStartDay !== 0) { customDayNames = [...dayNames.slice(weekStartDay), ...dayNames.slice(0, weekStartDay)]; customDayNamesShort = [...dayNamesShort.slice(weekStartDay), ...dayNamesShort.slice(0, weekStartDay)]; customizedHolidays = holidays.map(dayIndex => customDayNames.indexOf(dayNames[dayIndex])); } const childEle = /*#__PURE__*/React.createElement("div", { className: `${style.container} ${innerClass}`, "data-id": `${dataId}_Calendar`, "data-test-id": `${dataId}_Calendar`, onClick: this.closePopup }, renderCustomHeader, /*#__PURE__*/React.createElement(DateTimePopupHeader, { onOpenYearView: this.handleOpenYearView, showMonthTxt: showmonthtxt, isYearView: isYearView, prevMonthTitleText: prevMonthTitleText, nextMonthTitleText: nextMonthTitleText, onModifyCalendar: this.modifyCalendar }), /*#__PURE__*/React.createElement("div", { className: style.subContainer }, /*#__PURE__*/React.createElement(CalendarView, { needBorder: isDateTimeField || needAction, dataId: dataId, date: date, year: year, month: month, onSelect: this.dateSelect, dayNames: customDayNames, dayNamesShort: customDayNamesShort, todayDate: todayDate, todayMonth: todayMonth, todayYear: todayYear, weekStartDay: weekStartDay, holidays: customizedHolidays }), isDateTimeField ? /*#__PURE__*/React.createElement(Time, { timeText: timeText, dataId: dataId, hourSuggestions: getHourSuggestions(is24Hour), onHourSelect: this.hoursSelect, hours: hours, hourEmptyText: hourEmptyText, needResponsive: needResponsive, minSuggestions: getMinuteSuggestions(), onMinutesSelect: this.minutesSelect, mins: mins, minuteEmptyText: minuteEmptyText, ampmSuggestions: this.ampmSuggestions, onAmPmSelect: this.amPmSelect, amPm: amPm, is24Hour: is24Hour, customProps: TimeProps }) : null, needAction ? /*#__PURE__*/React.createElement(DateTimePopupFooter, { submitText: submitText, onSubmit: this.handleSelect, cancelText: cancelText, onCancel: this.handleClear, dataId: dataId }) : null, isYearView ? /*#__PURE__*/React.createElement("div", { className: style.yearContainer }, /*#__PURE__*/React.createElement(YearView, { onSelectYear: this.handleSelectYear, onSelectMonth: this.handleSelectMonthViaYearView, viewedYear: year, viewedMonth: month, monthNames: monthNames, monthNamesShort: monthNamesShort, isMonthOpen: isMonthOpen })) : null), renderCustomFooter); return isDefaultPosition ? /*#__PURE__*/React.createElement("div", { className: `${style.dropBox} ${className}`, "data-id": `${dataId}_dateBoxContainer`, "data-test-id": `${dataId}_dateBoxContainer` }, childEle) : isReady ? /*#__PURE__*/React.createElement(ResponsiveDropBox, _extends({ size: boxSize, boxPosition: position, isActive: isActive, isArrow: false, isAnimate: isAnimate, animationStyle: "bounce", getRef: getRef, onClick: onClick, dataId: `${dataId}_dateBoxContainer`, needResponsive: needResponsive, isAbsolutePositioningNeeded: isAbsolute, customClass: { customDropBoxWrap: className }, isPadding: isPadding, positionsOffset: positionsOffset, targetOffset: targetOffset, isRestrictScroll: isRestrictScroll, portalId: dropBoxPortalId }, DropBoxProps), /*#__PURE__*/React.createElement(Box, null, childEle)) : null; } } DateTime.propTypes = DateTime_propTypes; DateTime.defaultProps = DateTime_defaultProps;