@momentum-ui/react
Version:
Cisco Momentum UI framework for ReactJs applications
298 lines (260 loc) • 7.96 kB
JavaScript
/** @component date-picker */
import React from 'react';
import PropTypes from 'prop-types';
import { EventOverlay } from '@momentum-ui/react';
import DatePickerCalendar from '@momentum-ui/react/DatePicker/DatePickerCalendar';
import DatePickerContext from '@momentum-ui/react/DatePickerContext';
import {
addDays,
addWeeks,
isDayDisabled,
isSameDay,
subtractDays,
subtractWeeks,
} from '@momentum-ui/react/utils/dateUtils';
import moment from 'moment';
import omit from 'lodash/omit';
class DatePicker extends React.Component {
constructor(props) {
super(props);
this.state = {
anchorNode: null,
focus: null,
isOpen: false,
selected: null,
monthNavFocus: '',
};
}
componentDidMount() {
const selectedDate = moment(this.props.selectedDate);
const isValid =
selectedDate.isValid()
&& !isDayDisabled(selectedDate, this.props);
isValid && this.setPreSelection(selectedDate);
isValid && this.setSelected(selectedDate);
}
componentDidUpdate (prevProps) {
const selectedDate = moment(this.props.selectedDate);
const prevSelectedDate = moment(prevProps.selectedDate);
const isValid =
selectedDate.isValid()
&& !isDayDisabled(selectedDate, this.props);
if(
isValid
&& !isSameDay(prevSelectedDate, selectedDate)
) {
this.setSelected(selectedDate);
this.setPreSelection(selectedDate);
}
}
setOpen = open => {
this.setState({
isOpen: open,
});
};
handleSelect = (event, date) => {
const { shouldCloseOnSelect } = this.props;
this.setPreSelection(date, event);
this.setSelected(date, event);
shouldCloseOnSelect && this.setOpen(false);
};
setSelected = (date, event) => {
const { onSelect } = this.props;
!isDayDisabled(date, this.props)
&& this.setState({
selected: date,
}, () => onSelect && onSelect(event, date.toDate()));
};
setPreSelection = (date, event) => {
const { onChange } = this.props;
!isDayDisabled(date, this.props)
&& this.setState({
focus: date,
}, () => onChange && onChange(event, date.toDate()));
};
setMonthNavFocus = () => {
const { monthNavFocus } = this.state;
if (monthNavFocus === 'prev') {
this.setState({ monthNavFocus: 'next' });
} else {
this.setState({ monthNavFocus: 'prev' });
}
}
handleInputClick = () => {
this.setOpen(true);
};
handleInputKeyDown = event => {
const { focus, isOpen } = this.state;
let flag = false;
const copy = moment(focus);
const datePickerDayHasFocus = document.activeElement.className.includes('md-datepicker__day--focus');
const tabOverride = (event) => {
this.setMonthNavFocus();
event.preventDefault();
};
switch (!event.shiftKey && event.which) {
case 9: // Tab
isOpen && tabOverride(event);
break;
case 32:
case 13:
if(!isOpen) {
this.handleInputClick();
} else if (
(moment.isMoment(focus) ||
moment.isDate(focus)) &&
datePickerDayHasFocus
) {
this.handleSelect(event, copy);
}
flag = true;
break;
case 27: // escape
this.setOpen(false);
break;
case 38: // up
this.setPreSelection(subtractWeeks(copy, 1));
flag = true;
break;
case 37:// left
this.setPreSelection(subtractDays(copy, 1));
flag = true;
break;
case 39: // right
this.setPreSelection(addDays(copy, 1));
flag = true;
break;
case 40: // bottom
this.setPreSelection(addWeeks(copy, 1));
flag = true;
break;
default:
break;
}
switch (event.shiftKey && event.which) {
case 9: // Tab
isOpen && tabOverride(event);
break;
default:
break;
}
if (flag) {
event.stopPropagation();
event.preventDefault();
}
};
render() {
const {
children,
className,
direction,
isDynamic,
onMonthChange,
showArrow,
...props
} = this.props;
const { selected, focus, anchorNode, isOpen, monthNavFocus } = this.state;
const dpContext = {
handleDayClick: (event, date) => this.handleSelect(event, date),
handleMonthChange: (event, date) => onMonthChange && onMonthChange(event, date.toDate()),
focus,
selected,
};
const trigger = React.cloneElement(children, {
ref: ref => !this.state.anchorNode && this.setState({ anchorNode: ref}),
onClick: this.handleInputClick,
onKeyDown: this.handleInputKeyDown,
});
const otherProps = omit({...props}, ['onSelect', 'onChange', 'onMonthChange', 'shouldCloseOnSelect']);
const calendar = (
<DatePickerContext.Provider value={dpContext}>
<DatePickerCalendar
monthNavFocus={monthNavFocus}
{...otherProps}
/>
</DatePickerContext.Provider>
);
const content = (
<EventOverlay
allowClickAway
isOpen={isOpen}
anchorNode={anchorNode}
close={() => this.setOpen(false)}
direction={direction}
showArrow={showArrow}
isDynamic={isDynamic}
onKeyDown={this.handleInputKeyDown}
>
{calendar}
</EventOverlay>
);
return (
<div
className={
'md-datepicker-container' +
`${(className && ` ${className}`) || ''}`
}
>
{trigger}
{isOpen && content}
</div>
);
}
}
DatePicker.propTypes = {
/** @prop Children nodes to render inside DatePicker | null */
children: PropTypes.element,
/** @prop Optional css class string | '' */
className: PropTypes.string,
/** @prop Set the direction in which the DatePicker opens | 'bottom-center' */
direction: PropTypes.string,
/** @prop Function to filter Dates | null */
filterDate: PropTypes.func,
/** @prop Sets the DatePicker EventOverlay to be dynamic | true */
isDynamic: PropTypes.bool,
/** @prop Sets the language of the DatePicker | 'en' */
locale: PropTypes.string,
/** @prop Sets the last date in which the DatePicker does not disable | null */
maxDate: PropTypes.instanceOf(Date),
/** @prop Sets the first date in which the DatePicker does not disable | null */
minDate: PropTypes.instanceOf(Date),
/** @prop Sets the format of the month | 'MMMM YYYY' */
monthFormat: PropTypes.string,
/** @prop Text to display for blindness accessibility features | 'next' */
nextArialLabel: PropTypes.string,
/** @prop Handler invoked when user makes a chnage within the DatePicker | null */
onChange: PropTypes.func,
/** @prop Handler invoked when user makes a change to the selected month within DatePicker | null */
onMonthChange: PropTypes.func,
/** @prop Handler invoked when user selects a date within DatePicker | null */
onSelect: PropTypes.func,
/** @prop Text to display for blindness accessibility features | 'previous' */
previousArialLabel: PropTypes.string,
/** @prop Initial Selected Date for DatePicker | moment().toDate() */
selectedDate: PropTypes.instanceOf(Date),
/** @prop Determines if the DatePicker should close when a date is selected | true */
shouldCloseOnSelect: PropTypes.bool,
/** @prop Determines if the DatePicker should show the open/close arrow | false */
showArrow: PropTypes.bool,
};
DatePicker.defaultProps = {
children: null,
className: '',
direction: 'bottom-center',
filterDate: null,
isDynamic: true,
locale: 'en',
maxDate: null,
minDate: null,
monthFormat: 'MMMM YYYY',
nextArialLabel: '',
onChange: null,
onMonthChange: null,
onSelect: null,
previousArialLabel: '',
selectedDate: moment().toDate(),
shouldCloseOnSelect: true,
showArrow: false,
};
DatePicker.displayName = 'DatePicker';
export default DatePicker;