react-datepicker-mobin
Version:
react datepicker component. (include persian jalaali calendar)
391 lines (343 loc) • 12.3 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import momentJalaali from 'moment-jalaali';
import TetherComponent from 'react-tether';
import classnames from 'classnames';
import Calendar from './Calendar';
import MyTimePicker from './CustomTimePicker';
const outsideClickIgnoreClass = 'ignore--click--outside';
export default class DatePicker extends Component {
static propTypes = {
value: PropTypes.object,
defaultValue: PropTypes.object,
onChange: PropTypes.func,
onInputChange: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
children: PropTypes.node,
min: PropTypes.object,
max: PropTypes.object,
defaultYear: PropTypes.object,
defaultMonth: PropTypes.object,
inputFormat: PropTypes.string,
inputJalaaliFormat: PropTypes.string,
removable: PropTypes.bool,
styles: PropTypes.object,
calendarStyles: PropTypes.object,
calendarContainerProps: PropTypes.object,
isGregorian: PropTypes.bool, // jalaali or gregorian
timePicker: PropTypes.bool,
calendarClass: PropTypes.string,
datePickerClass: PropTypes.string,
tetherAttachment: PropTypes.string,
inputReadOnly: PropTypes.bool,
ranges: PropTypes.array,
showToggleButton: PropTypes.bool,
toggleButtonText: PropTypes.any,
showTodayButton: PropTypes.bool,
placeholder: PropTypes.string,
name: PropTypes.string,
persianDigits: PropTypes.bool,
setTodayOnBlur: PropTypes.bool,
disableYearSelector: PropTypes.bool,
};
static defaultProps = {
styles: undefined,
calendarContainerProps: {},
isGregorian: true,
timePicker: true,
showTodayButton: true,
placeholder: '',
name: '',
persianDigits: true,
setTodayOnBlur: true,
disableYearSelector: false,
};
constructor(props) {
super(props);
// create a ref to store the textInput DOM element
this.textInput = React.createRef();
this.state = {
isOpen: false,
momentValue: this.props.defaultValue || null,
inputValue: this.getValue(
this.props.defaultValue,
this.props.isGregorian,
this.props.timePicker
),
inputJalaaliFormat:
this.props.inputJalaaliFormat || this.getInputFormat(false, this.props.timePicker),
inputFormat: this.props.inputFormat || this.getInputFormat(true, this.props.timePicker),
isGregorian: this.props.isGregorian,
timePicker: this.props.timePicker,
timePickerComponent: this.props.timePicker ? MyTimePicker : undefined,
setTodayOnBlur: this.props.setTodayOnBlur
};
}
getInputFormat(isGregorian, timePicker) {
if (timePicker) return isGregorian ? 'YYYY/M/D hh:mm A' : 'jYYYY/jM/jD hh:mm A';
return isGregorian ? 'YYYY/M/D' : 'jYYYY/jM/jD';
}
getValue(inputValue, isGregorian, timePicker) {
if (!inputValue) return '';
let { inputFormat } = this.state;
let { inputJalaaliFormat } = this.state;
if (!inputFormat) inputFormat = this.getInputFormat(isGregorian, timePicker);
if (!inputJalaaliFormat) inputJalaaliFormat = this.getInputFormat(isGregorian, timePicker);
return isGregorian
? inputValue.locale('es').format(inputFormat)
: inputValue.locale('fa').format(inputJalaaliFormat);
}
setOpen = isOpen => {
this.setState({ isOpen });
if (this.props.onOpen) {
this.props.onOpen(isOpen);
}
};
UNSAFE_componentWillMount() {
if (this.props.value) {
this.setMomentValue(this.props.value);
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
if ('value' in nextProps) {
if (nextProps.value === null) {
this.setState({
input: '',
inputValue: '',
momentValue: null
});
}
else if ((typeof nextProps.value === 'undefined' && typeof this.props.value !== 'undefined') ||
(typeof nextProps.value !== 'undefined' && !nextProps.value.isSame(this.props.value))
) {
this.setMomentValue(nextProps.value);
}
}
if ('isGregorian' in nextProps && nextProps.isGregorian !== this.props.isGregorian) {
const { inputFormat: nextPropsInputFormat } = nextProps;
const { inputJalaaliFormat: nextPropsInputJalaaliFormat } = nextProps;
this.setState({
isGregorian: nextProps.isGregorian,
inputValue: this.getValue(nextProps.value, nextProps.isGregorian, nextProps.timePicker),
inputFormat: nextPropsInputFormat || this.state.inputFormat,
inputJalaaliFormat: nextPropsInputJalaaliFormat || this.state.inputJalaaliFormat
});
}
if ('timePicker' in nextProps && nextProps.timePicker !== this.props.timePicker) {
this.setState({
timePicker: nextProps.timePicker,
timePickerComponent: this.props.timePicker ? MyTimePicker : undefined
});
}
if ('setTodayOnBlur' in nextProps && nextProps.setTodayOnBlur !== this.props.setTodayOnBlur) {
this.setState({
setTodayOnBlur: nextProps.setTodayOnBlur,
});
}
}
toggleMode = () => {
const isGregorian = !this.state.isGregorian;
const { inputFormat: nextPropsInputFormat } = this.props;
const { inputJalaaliFormat: nextPropsInputJalaaliFormat } = this.props;
this.setState({
isGregorian: isGregorian,
inputValue: this.getValue(this.props.value, isGregorian, this.props.timePicker)
});
};
setMomentValue(momentValue) {
const { inputFormat, isGregorian, timePicker } = this.state;
if (this.props.onChange) {
this.props.onChange(momentValue);
}
const inputValue = this.getValue(momentValue, isGregorian, timePicker);
this.setState({ momentValue, inputValue });
}
handleFocus = () => {
this.setOpen(true);
};
handleClickOutsideCalendar() {
this.setOpen(false);
}
toEnglishDigits(str) {
if (!str) return str;
const regex1 = /[\u0660-\u0669]/g;
const regex2 = /[\u06f0-\u06f9]/g;
return str
.replace(regex1, function (c) {
return c.charCodeAt(0) - 0x0660;
})
.replace(regex2, function (c) {
return c.charCodeAt(0) - 0x06f0;
});
}
toPersianDigits(str) {
if (!str) return str;
const regex = /[0-9]/g;
const id = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
return str.replace(regex, function (w) {
return id[+w];
});
}
handleSelectDay(selectedDay) {
const { momentValue: oldValue } = this.state;
let momentValue = selectedDay.clone();
if (oldValue) {
momentValue = momentValue.set({
hour: oldValue.hours(),
minute: oldValue.minutes(),
second: oldValue.seconds()
});
}
this.setOpen(false);
this.setMomentValue(momentValue);
}
handleInputChange(event) {
const { inputFormat, inputJalaaliFormat, isGregorian } = this.state;
const inputValue = this.toEnglishDigits(event.target.value);
const currentInputFormat = isGregorian ? inputFormat : inputJalaaliFormat;
const momentValue = momentJalaali(inputValue, currentInputFormat);
const cursor = event.target.selectionStart;
if (momentValue.isValid()) {
this.setState({ momentValue });
}
this.setState({ inputValue }, () => {
// It cause lose current cursor positon if persian digits is active
// for example it convert 4 to ۴, so the react set cursor position to end of string
if (this.props.persianDigits) this.input.setSelectionRange(cursor, cursor);
});
if (this.props.onInputChange) {
this.props.onInputChange(event);
}
}
hanldeBlur(event) {
if (this.props.onChange) {
if (!event.target.value && this.state.setTodayOnBlur === false)
return;
const { inputFormat, inputJalaaliFormat, isGregorian } = this.state;
const inputValue = this.toEnglishDigits(event.target.value);
const currentInputFormat = isGregorian ? inputFormat : inputJalaaliFormat;
const momentValue = momentJalaali(inputValue, currentInputFormat);
if (event.target.value && momentValue.isValid()) {
this.props.onChange(this.state.momentValue);
} else if (this.state.setTodayOnBlur === true) {
this.props.onChange(momentJalaali());
}
}
}
handleInputClick() {
if (!this.props.disabled) {
this.setOpen(true);
}
}
renderInput = ref => {
const { isOpen, inputValue, isGregorian } = this.state;
const className = classnames(this.props.className, {
[outsideClickIgnoreClass]: isOpen
});
return (
<div ref={ref}>
<input
placeholder={this.props.placeholder}
name={this.props.name}
className={`datepicker-input ${className}`}
type="text"
ref={inst => {
this.input = inst;
}}
onFocus={this.handleFocus.bind(this)}
onBlur={this.hanldeBlur.bind(this)}
onChange={this.handleInputChange.bind(this)}
onClick={this.handleInputClick.bind(this)}
value={
isGregorian || !this.props.persianDigits ? inputValue : this.toPersianDigits(inputValue)
}
readOnly={this.props.inputReadOnly === true}
disabled={this.props.disabled}
/>
</div>
);
};
renderCalendar = ref => {
const { momentValue, isGregorian, timePickerComponent: TimePicker } = this.state;
const {
onChange,
min,
max,
defaultYear,
defaultMonth,
styles,
calendarContainerProps,
ranges,
disableYearSelector,
} = this.props;
return (
<div ref={ref}>
<Calendar
toggleMode={this.toggleMode}
ranges={ranges}
min={min}
max={max}
selectedDay={momentValue}
defaultYear={defaultYear}
defaultMonth={defaultMonth}
onSelect={this.handleSelectDay.bind(this)}
onClickOutside={this.handleClickOutsideCalendar.bind(this)}
outsideClickIgnoreClass={outsideClickIgnoreClass}
styles={styles}
containerProps={calendarContainerProps}
isGregorian={isGregorian}
calendarClass={this.props.calendarClass ? this.props.calendarClass : ''}
showToggleButton={this.props.showToggleButton}
toggleButtonText={this.props.toggleButtonText}
showTodayButton={this.props.showTodayButton}
disableYearSelector={disableYearSelector}
timePicker={
TimePicker ? (
<TimePicker
outsideClickIgnoreClass={outsideClickIgnoreClass}
isGregorian={isGregorian}
min={min}
max={max}
momentValue={momentValue}
setMomentValue={this.setMomentValue.bind(this)}
/>
) : null
}
/>
</div>
);
};
removeDate() {
const { onChange } = this.props;
if (onChange) {
onChange(null);
}
this.setState({
input: '',
inputValue: '',
momentValue: null
});
}
render() {
const { isOpen } = this.state;
return (
<TetherComponent
ref={tether => (this.tether = tether)}
attachment={this.props.tetherAttachment ? this.props.tetherAttachment : 'top center'}
constraints={[
{
to: 'window',
attachment: 'together'
}
]}
offset="-10px -10px"
onResize={() => this.tether && this.tether.position()}
/* renderTarget: This is what the item will be tethered to, make sure to attach the ref */
renderTarget={ref => this.renderInput(ref)}
/* renderElement: If present, this item will be tethered to the the component returned by renderTarget */
renderElement={ref => isOpen && this.renderCalendar(ref)}
/>
);
}
}