@zohodesk/components
Version:
In this Package, we Provide Some Basic Components to Build Web App
779 lines (712 loc) • 20.3 kB
JavaScript
/* 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/DateTime.module.css";
import { formatDate, getMonthEnd } from "../../utils/datetime/common";
import { getIsEmptyValue } from "../../utils/Common";
import { monthNamesDefault, monthNamesShortDefault, dayNamesDefault, dayNamesShortDefault, ampmTextDefault } from "../../DateTime/constants";
import ResponsiveDropBox from "../ResponsiveDropBox/ResponsiveDropBox";
import { Box } from "../Layout";
import { getHourSuggestions, getMinuteSuggestions, addZeroIfNeeded } from "../../DateTime/dateFormatUtils";
import { getDateText } from "./../../DateTime/dateFormatUtils/dateFormat";
function title(date, year, month) {
let monthNames = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
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, timeZone, isDateTimeField);
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() {
let selectedInfo = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
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() {
let selectedInfo = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
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,
startDate,
endDate,
customProps = {},
holidays,
weekStartDay
} = this.props;
const {
TimeProps = {}
} = 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
}, /*#__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,
startDate: startDate,
endDate: endDate,
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));
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, {
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
}, /*#__PURE__*/React.createElement(Box, null, childEle)) : null;
}
}
DateTime.propTypes = DateTime_propTypes;
DateTime.defaultProps = DateTime_defaultProps;