material-ui-arabic-datepicker
Version:
React components, that implements material design date and time pickers for material-ui v1 including a customized Datepicker with arabic header, months and weekdays and English numbers, modfied implementation from materail-ui-pickers
343 lines (304 loc) • 8.93 kB
JSX
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Icon from '@material-ui/core/Icon';
import InputAdornment from '@material-ui/core/InputAdornment';
import TextField from '@material-ui/core/TextField';
import IconButton from '@material-ui/core/IconButton';
import withStyles from '@material-ui/core/styles/withStyles';
import DomainPropTypes from '../constants/prop-types';
import MaskedInput from './MaskedInput';
import withUtils from '../_shared/WithUtils';
const getDisplayDate = (props) => {
const {
utils, value, format, invalidLabel, emptyLabel, labelFunc,
} = props;
const isEmpty = value === null;
const date = utils.date(value);
if (labelFunc) {
return labelFunc(isEmpty ? null : date, invalidLabel);
}
if (isEmpty) {
return emptyLabel;
}
return utils.isValid(date)
? utils.format(date, format)
: invalidLabel;
};
const getError = (value, props) => {
const {
utils,
maxDate,
minDate,
disablePast,
disableFuture,
maxDateMessage,
minDateMessage,
invalidDateMessage,
} = props;
if (!utils.isValid(value)) {
// if null - do not show error
if (utils.isNull(value)) {
return '';
}
return invalidDateMessage;
}
if (
(maxDate && utils.isAfter(value, maxDate)) ||
(disableFuture && utils.isAfter(value, utils.endOfDay(utils.date())))
) {
return maxDateMessage;
}
if (
(minDate && utils.isBefore(value, minDate)) ||
(disablePast && utils.isBefore(value, utils.startOfDay(utils.date())))
) {
return minDateMessage;
}
return '';
};
export class DateTextField extends PureComponent {
static updateState = props => ({
value: props.value,
displayValue: getDisplayDate(props),
error: getError(props.utils.date(props.value), props),
});
static propTypes = {
classes: PropTypes.shape({}).isRequired,
value: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Date),
]),
minDate: DomainPropTypes.date,
maxDate: DomainPropTypes.date,
disablePast: PropTypes.bool,
disableFuture: PropTypes.bool,
format: PropTypes.string,
onBlur: PropTypes.func,
onChange: PropTypes.func.isRequired,
onClear: PropTypes.func,
onClick: PropTypes.func.isRequired,
clearable: PropTypes.bool,
utils: PropTypes.object.isRequired,
disabled: PropTypes.bool,
InputProps: PropTypes.shape(),
/** Input mask, used in keyboard mode read more <a href="https://github.com/text-mask/text-mask/blob/master/componentDocumentation.md#readme">here</a> */
mask: PropTypes.any,
/** Error message, shown if date is less then minimal date */
minDateMessage: PropTypes.node,
/** Error message, shown if date is more then maximal date */
maxDateMessage: PropTypes.node,
/** Message displaying in text field, if date is invalid (doesn't work in keyboard mode) */
invalidLabel: PropTypes.string,
/** Message displaying in text field, if null passed (doesn't work in keyboard mode) */
emptyLabel: PropTypes.string,
/** Dynamic label generation function (date, invalidLabel) => string */
labelFunc: PropTypes.func,
/** On/off manual keyboard input mode */
keyboard: PropTypes.bool,
/** Icon displayed for open picker button in keyboard mode */
keyboardIcon: PropTypes.node,
/** enables/disable automatic opening of the picker when the user clicks enter */
disableOpenOnEnter: PropTypes.bool,
/** Message, appearing when date cannot be parsed */
invalidDateMessage: PropTypes.node,
/** Component that should replace the default Material-UI TextField */
TextFieldComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/** Props to pass to keyboard input adornment */
InputAdornmentProps: PropTypes.object,
/** Specifies position of keyboard button adornment */
adornmentPosition: PropTypes.oneOf(['start', 'end']),
/** Callback firing when date that applied in the keyboard is invalid */
onError: PropTypes.func,
}
static defaultProps = {
disabled: false,
invalidLabel: 'Unknown',
emptyLabel: '',
value: new Date(),
labelFunc: undefined,
format: undefined,
InputProps: undefined,
keyboard: false,
mask: undefined,
keyboardIcon: 'event',
disableOpenOnEnter: false,
invalidDateMessage: 'Invalid Date Format',
clearable: false,
onBlur: undefined,
onClear: undefined,
disablePast: false,
disableFuture: false,
onError: undefined,
minDate: '1900-01-01',
maxDate: '2100-01-01',
minDateMessage: 'Date should not be before minimal date',
maxDateMessage: 'Date should not be after maximal date',
TextFieldComponent: TextField,
InputAdornmentProps: {},
adornmentPosition: 'end',
}
state = DateTextField.updateState(this.props)
componentDidUpdate(prevProps) {
if (
!this.props.utils.isEqual(this.props.value, prevProps.value) ||
prevProps.format !== this.props.format ||
prevProps.maxDate !== this.props.maxDate ||
prevProps.minDate !== this.props.minDate ||
prevProps.emptyLabel !== this.props.emptyLabel ||
prevProps.utils !== this.props.utils
) {
/* eslint-disable-next-line react/no-did-update-set-state */
this.setState(DateTextField.updateState(this.props));
}
}
commitUpdates = (value) => {
const {
clearable,
onClear,
utils,
format,
onError,
} = this.props;
if (value === '') {
if (this.props.value === null) {
this.setState(DateTextField.updateState(this.props));
} else if (clearable && onClear) {
onClear();
}
return;
}
const oldValue = utils.date(this.state.value);
const newValue = utils.parse(value, format);
const error = getError(newValue, this.props);
this.setState({
error,
displayValue: value,
value: error ? newValue : oldValue,
}, () => {
if (!error && !utils.isEqual(newValue, oldValue)) {
this.props.onChange(newValue);
}
if (error && onError) {
onError(newValue, error);
}
});
}
handleBlur = (e) => {
if (this.props.keyboard) {
e.preventDefault();
e.stopPropagation();
this.commitUpdates(e.target.value);
if (this.props.onBlur) {
this.props.onBlur(e);
}
}
};
handleChange = (e) => {
const { utils, format } = this.props;
const parsedValue = utils.parse(e.target.value, format);
this.setState({
displayValue: e.target.value,
error: getError(parsedValue, this.props),
});
}
handleFocus = (e) => {
e.stopPropagation();
e.preventDefault();
if (!this.props.keyboard) {
this.openPicker(e);
}
}
handleKeyPress = (e) => {
if (e.key === 'Enter') {
if (!this.props.disableOpenOnEnter) {
this.openPicker(e);
} else {
this.commitUpdates(e.target.value);
}
}
}
openPicker = (e) => {
const { disabled, onClick } = this.props;
if (!disabled) {
onClick(e);
}
}
render() {
const {
adornmentPosition,
classes,
clearable,
disabled,
disableFuture,
disableOpenOnEnter,
disablePast,
emptyLabel,
format,
InputAdornmentProps,
InputProps,
invalidDateMessage,
invalidLabel,
keyboard,
keyboardIcon,
labelFunc,
mask,
maxDate,
maxDateMessage,
minDate,
minDateMessage,
onBlur,
onClear,
onClick,
TextFieldComponent,
utils,
value,
...other
} = this.props;
const { displayValue, error } = this.state;
const localInputProps = {
className: classes.input,
inputComponent: MaskedInput,
inputProps: {
mask: !keyboard ? null : mask,
readOnly: !keyboard,
},
};
if (keyboard) {
localInputProps[`${adornmentPosition}Adornment`] = (
<InputAdornment
position={adornmentPosition}
{...InputAdornmentProps}
>
<IconButton
disabled={disabled}
onClick={this.openPicker}
>
<Icon> {keyboardIcon} </Icon>
</IconButton>
</InputAdornment>
);
}
return (
<TextFieldComponent
onClick={this.handleFocus}
error={!!error}
helperText={error}
onKeyPress={this.handleKeyPress}
onBlur={this.handleBlur}
disabled={disabled}
value={displayValue}
{...other}
onChange={this.handleChange}
InputProps={{ ...localInputProps, ...InputProps }}
/>
);
}
}
const styles = {
input: {
alignItems: 'flex-end',
},
};
export default withStyles(styles)(withUtils()(DateTextField));