DocSpace-client/packages/components/date-picker/index.js
2023-01-27 17:08:30 +03:00

450 lines
11 KiB
JavaScript

import React, { Component } from "react";
import PropTypes from "prop-types";
import CalendarReactSvgUrl from "PUBLIC_DIR/images/calendar.react.svg?url";
import InputBlock from "../input-block";
import DropDown from "../drop-down";
import Calendar from "../calendar";
import moment from "moment";
import { handleAnyClick } from "../utils/event";
import isEmpty from "lodash/isEmpty";
import Aside from "../aside";
import { desktop } from "../utils/device";
import Backdrop from "../backdrop";
import Heading from "../heading";
import throttle from "lodash/throttle";
import {
Body,
Header,
Content,
DropDownStyle,
DateInputStyle,
} from "./styled-date-picker";
class DatePicker extends Component {
constructor(props) {
super(props);
moment.locale(props.locale);
this.ref = React.createRef();
const { isOpen, selectedDate, hasError, minDate, maxDate } = this.props;
if (isOpen) {
handleAnyClick(true, this.handleClick);
}
let newState = {
isOpen,
selectedDate: moment(selectedDate).toDate(),
value: moment(selectedDate).format("L"),
mask: this.getMask,
hasError,
displayType: this.getTypeByWidth(),
};
if (this.isValidDate(selectedDate, maxDate, minDate, hasError)) {
newState = Object.assign({}, newState, {
hasError: true,
isOpen: false,
});
}
this.state = newState;
this.throttledResize = throttle(this.resize, 300);
}
handleClick = (e) => {
this.state.isOpen &&
!this.ref.current.contains(e.target) &&
this.onClick(false);
};
handleChange = (e) => {
const { value } = this.state;
const targetValue = e.target.value;
if (value != targetValue) {
let newState = { value: targetValue };
const format = moment.localeData().longDateFormat("L");
const momentDate = moment(targetValue, format);
const date = momentDate.toDate();
if (
!isNaN(date) &&
this.compareDate(date) &&
targetValue.indexOf("_") === -1
) {
//console.log("Mask complete");
this.props.onChange && this.props.onChange(date);
newState = Object.assign({}, newState, {
selectedDate: date,
hasError: false,
});
} else if (targetValue.indexOf("_") !== -1 && targetValue.length !== 0) {
//hasWarning
newState = Object.assign({}, newState, {
hasError: true,
});
} else {
newState = Object.assign({}, newState, {
hasError: true,
isOpen: false,
});
}
this.setState(newState);
}
};
onChange = (value) => {
const formatValue = moment(value).format("L");
this.props.onChange && this.props.onChange(value);
this.setState({
selectedDate: value,
value: formatValue,
hasError: false,
isOpen: !this.state.isOpen,
});
};
onClick = () => {
this.setState({ isOpen: !this.state.isOpen });
};
onClose = () => {
this.setState({ isOpen: false });
};
compareDate = (date) => {
const { minDate, maxDate } = this.props;
if (date < minDate || date > maxDate) {
return false;
}
return true;
};
getMask = () => {
let symbol = ".";
const localeMask = moment.localeData().longDateFormat("L");
const { locale } = this.props;
if (localeMask.indexOf("/") + 1) {
symbol = "/";
} else if (localeMask.indexOf(".") + 1) {
symbol = ".";
} else if (localeMask.indexOf("-") + 1) {
symbol = "-";
}
const mask = [
/\d/,
/\d/,
symbol,
/\d/,
/\d/,
symbol,
/\d/,
/\d/,
/\d/,
/\d/,
];
if (localeMask[0] === "Y") {
mask.reverse();
}
if (locale === "ko" || locale === "lv") {
mask.push(symbol);
}
return mask;
};
compareDates = (date1, date2) => {
return moment(date1)
.startOf("day")
.diff(moment(date2).startOf("day"), "days");
};
isValidDate = (selectedDate, maxDate, minDate, hasError) => {
if (
(this.compareDates(selectedDate, maxDate) > 0 ||
this.compareDates(selectedDate, minDate) < 0) &&
!hasError
) {
return true;
}
return false;
};
getTypeByWidth = () => {
if (this.props.displayType !== "auto") return this.props.displayType;
return window.innerWidth < desktop.match(/\d+/)[0] ? "aside" : "dropdown";
};
resize = () => {
if (this.props.displayType !== "auto") return;
const type = this.getTypeByWidth();
if (type === this.state.displayType) return;
this.setState({ displayType: type });
};
popstate = () => {
window.removeEventListener("popstate", this.popstate, false);
this.onClose();
window.history.go(1);
};
componentDidMount() {
window.addEventListener("resize", this.throttledResize);
}
componentWillUnmount() {
window.removeEventListener("resize", this.throttledResize);
handleAnyClick(false, this.handleClick);
}
componentDidUpdate(prevProps, prevState) {
const {
locale,
isOpen,
selectedDate,
maxDate,
minDate,
displayType,
} = this.props;
const { hasError, value } = this.state;
let newState = {};
if (locale !== prevProps.locale) {
moment.locale(locale);
newState = {
mask: this.getMask(),
value: moment(selectedDate).format("L"),
};
}
if (selectedDate !== prevProps.selectedDate) {
newState = Object.assign({}, newState, {
selectedDate,
value: moment(selectedDate).format("L"),
});
}
if (this.state.isOpen !== prevState.isOpen) {
handleAnyClick(this.state.isOpen, this.handleClick);
}
if (isOpen !== prevProps.isOpen) {
newState = Object.assign({}, newState, {
isOpen,
});
}
if (this.props.hasError !== prevProps.hasError) {
newState = Object.assign({}, newState, {
hasError: this.props.hasError,
isOpen: false,
});
}
const date = new Date(value);
if (
this.compareDates(selectedDate, maxDate) <= 0 &&
this.compareDates(selectedDate, minDate) >= 0 &&
hasError &&
this.compareDates(date, maxDate) <= 0 &&
this.compareDates(date, minDate) >= 0 &&
!this.props.hasError
) {
newState = Object.assign({}, newState, {
hasError: false,
selectedDate,
value: moment(selectedDate).format("L"),
});
}
if (
this.isValidDate(selectedDate, maxDate, minDate, hasError) &&
this.isValidDate(this.state.selectedDate, maxDate, minDate, hasError)
) {
newState = Object.assign({}, newState, {
hasError: true,
isOpen: false,
});
}
if (displayType !== prevProps.displayType) {
newState = Object.assign({}, newState, {
displayType: this.getTypeByWidth(),
});
}
if (isOpen && this.state.displayType === "aside") {
window.addEventListener("popstate", this.popstate, false);
}
if (!isEmpty(newState)) {
this.setState(newState);
}
}
renderBody = () => {
const { isDisabled, minDate, maxDate, locale, themeColor } = this.props;
const { selectedDate, displayType } = this.state;
let calendarSize;
displayType === "aside" ? (calendarSize = "big") : (calendarSize = "base");
return (
<Calendar
locale={locale}
themeColor={themeColor}
minDate={minDate}
maxDate={maxDate}
isDisabled={isDisabled}
openToDate={selectedDate}
selectedDate={selectedDate}
onChange={this.onChange}
size={calendarSize}
/>
);
};
render() {
const {
isDisabled,
isReadOnly,
zIndex,
calendarHeaderContent,
id,
style,
className,
inputClassName,
fixedDirection,
} = this.props;
const { value, isOpen, mask, hasError, displayType } = this.state;
return (
<DateInputStyle
ref={this.ref}
id={id}
className={className}
style={style}
>
<InputBlock
className={inputClassName}
scale={true}
isDisabled={isDisabled}
isReadOnly={isReadOnly}
hasError={hasError}
iconName={CalendarReactSvgUrl}
// iconColor="#A3A9AE"
// hoverColor="#A3A9AE"
onIconClick={this.onClick}
value={value}
onChange={this.handleChange}
mask={mask}
keepCharPositions={true}
//guide={true}
//showMask={true}
/>
{isOpen ? (
displayType === "dropdown" ? (
<DropDownStyle>
<DropDown
forwardedRef={this.ref}
className="drop-down"
open={isOpen}
clickOutsideAction={this.onClose}
isDefaultMode={false}
fixedDirection={fixedDirection}
withBlur={window.innerWidth <= 428}
zIndex={220}
>
{this.renderBody()}
</DropDown>
</DropDownStyle>
) : (
<>
<Backdrop
onClick={this.onClose}
visible={isOpen}
zIndex={zIndex}
isAside={true}
/>
<Aside
visible={isOpen}
scale={false}
zIndex={zIndex}
onClose={this.onClose}
>
<Content>
<Header>
<Heading className="header" size="medium" truncate={true}>
{calendarHeaderContent}
</Heading>
</Header>
<Body>{this.renderBody()}</Body>
</Content>
</Aside>
</>
)
) : null}
</DateInputStyle>
);
}
}
DatePicker.propTypes = {
/** Function called when the user select a day */
onChange: PropTypes.func,
/** Color of the selected day */
themeColor: PropTypes.string,
/** Selected date value */
selectedDate: PropTypes.instanceOf(Date),
/** Opened date value */
openToDate: PropTypes.instanceOf(Date),
/** Minimum date that the user can select */
minDate: PropTypes.instanceOf(Date),
/** Maximum date that the user can select */
maxDate: PropTypes.instanceOf(Date),
/** Browser locale */
locale: PropTypes.string,
/** Disabled react-calendar */
isDisabled: PropTypes.bool,
/** Set input type is read only */
isReadOnly: PropTypes.bool,
/** Set error date-input style */
hasError: PropTypes.bool,
//hasWarning: PropTypes.bool,
/** Opens calendar */
isOpen: PropTypes.bool,
/** Calendar size */
calendarSize: PropTypes.oneOf(["base", "big"]),
/** Calendar display type */
displayType: PropTypes.oneOf(["dropdown", "aside", "auto"]),
/** Calendar css z-index */
zIndex: PropTypes.number,
/** Calendar header content (calendar opened in aside) */
calendarHeaderContent: PropTypes.string,
/** Accepts class */
className: PropTypes.string,
/** Accepts input class */
inputClassName: PropTypes.string,
/** Accepts id */
id: PropTypes.string,
/** Accepts css style */
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
fixedDirection: PropTypes.bool,
};
DatePicker.defaultProps = {
minDate: new Date("1970/01/01"),
maxDate: new Date(new Date().getFullYear() + 1, 1, 1),
selectedDate: moment(new Date()).toDate(),
displayType: "auto",
zIndex: 310,
};
export default DatePicker;