From 8268d667a76a9332b674cce9f4899ca50596a378 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 12 Jul 2018 14:56:22 -0700 Subject: [PATCH 1/8] Store interal value as array --- lib/src/DatePicker/Calendar.jsx | 25 ++++++------ lib/src/DatePicker/DatePicker.jsx | 8 ++-- lib/src/DatePicker/DatePickerWrapper.jsx | 4 +- lib/src/DatePicker/YearSelection.jsx | 6 +-- lib/src/_shared/BasePicker.jsx | 10 ++--- lib/src/_shared/DateTextField.jsx | 48 ++++++++++++------------ lib/src/constants/prop-types.js | 8 +++- lib/src/utils/date-fns-utils.js | 12 ++++++ lib/src/utils/luxon-utils.js | 12 ++++++ lib/src/utils/moment-utils.js | 12 ++++++ lib/src/wrappers/ModalWrapper.jsx | 4 +- 11 files changed, 94 insertions(+), 55 deletions(-) diff --git a/lib/src/DatePicker/Calendar.jsx b/lib/src/DatePicker/Calendar.jsx index c2ab2ed0a..6577ab6ca 100644 --- a/lib/src/DatePicker/Calendar.jsx +++ b/lib/src/DatePicker/Calendar.jsx @@ -14,7 +14,7 @@ import { findClosestEnabledDate } from '../_helpers/date-utils'; /* eslint-disable no-unused-expressions */ export class Calendar extends Component { static propTypes = { - date: PropTypes.object.isRequired, + date: DomainPropTypes.dateRange.isRequired, minDate: DomainPropTypes.date, maxDate: DomainPropTypes.date, classes: PropTypes.object.isRequired, @@ -43,14 +43,14 @@ export class Calendar extends Component { }; state = { - currentMonth: this.props.utils.getStartOfMonth(this.props.date), + currentMonth: this.props.utils.getStartOfMonth(this.props.date[this.props.date.length - 1]), }; static getDerivedStateFromProps(nextProps, state) { if (!nextProps.utils.isEqual(nextProps.date, state.lastDate)) { return { lastDate: nextProps.date, - currentMonth: nextProps.utils.getStartOfMonth(nextProps.date), + currentMonth: nextProps.utils.getStartOfMonth(nextProps.date[nextProps.date.length - 1]), }; } @@ -62,26 +62,27 @@ export class Calendar extends Component { date, minDate, maxDate, utils, disableFuture, disablePast, } = this.props; - if (this.shouldDisableDate(date)) { + date.forEach(day => + this.shouldDisableDate(day) && this.onDateSelect(findClosestEnabledDate({ - date, + day, utils, minDate, maxDate, disablePast, disableFuture, shouldDisableDate: this.shouldDisableDate, - }), false); - } + }), false) + ); } onDateSelect = (day, isFinish = true) => { const { date, utils } = this.props; - const withHours = utils.setHours(day, utils.getHours(date)); - const withMinutes = utils.setMinutes(withHours, utils.getMinutes(date)); + const withHours = utils.setHours(day, utils.getHours(date[0])); + const withMinutes = utils.setMinutes(withHours, utils.getMinutes(date[0])); - this.props.onChange(withMinutes, isFinish); + this.props.onChange([ withMinutes ], isFinish); }; handleChangeMonth = (newMonth) => { @@ -185,7 +186,7 @@ export class Calendar extends Component { renderDays = (week) => { const { date, renderDay, utils } = this.props; - const selectedDate = utils.startOfDay(date); + const selectedDate = date.map(utils.startOfDay); const currentMonthNumber = utils.getMonth(this.state.currentMonth); const now = utils.date(); @@ -198,7 +199,7 @@ export class Calendar extends Component { current={utils.isSameDay(day, now)} hidden={!dayInCurrentMonth} disabled={disabled} - selected={utils.isSameDay(selectedDate, day)} + selected={selectedDate.some(o => utils.isSameDay(o, day))} > {utils.getDayText(day)} diff --git a/lib/src/DatePicker/DatePicker.jsx b/lib/src/DatePicker/DatePicker.jsx index 8cc617203..d960e2290 100644 --- a/lib/src/DatePicker/DatePicker.jsx +++ b/lib/src/DatePicker/DatePicker.jsx @@ -10,7 +10,7 @@ import withUtils from '../_shared/WithUtils'; export class DatePicker extends PureComponent { static propTypes = { - date: PropTypes.object.isRequired, + date: DomainPropTypes.dateRange.isRequired, minDate: DomainPropTypes.date, maxDate: DomainPropTypes.date, onChange: PropTypes.func.isRequired, @@ -47,7 +47,7 @@ export class DatePicker extends PureComponent { } get date() { - return this.props.utils.startOfDay(this.props.date); + return this.props.date.map(this.props.utils.startOfDay); } get minDate() { @@ -93,14 +93,14 @@ export class DatePicker extends PureComponent { variant="subheading" onClick={this.openYearSelection} selected={showYearSelection} - label={utils.getYearText(this.date)} + label={utils.getYearText(this.date[this.date.length - 1])} /> diff --git a/lib/src/DatePicker/DatePickerWrapper.jsx b/lib/src/DatePicker/DatePickerWrapper.jsx index 0058f6585..c593a1b9d 100644 --- a/lib/src/DatePicker/DatePickerWrapper.jsx +++ b/lib/src/DatePicker/DatePickerWrapper.jsx @@ -82,7 +82,7 @@ export const DatePickerWrapper = (props) => { DatePickerWrapper.propTypes = { /** Datepicker value */ - value: DomainPropTypes.date, + value: DomainPropTypes.dateRange, /** Min selectable date */ minDate: DomainPropTypes.date, /** Max selectable date */ @@ -117,7 +117,7 @@ DatePickerWrapper.propTypes = { }; DatePickerWrapper.defaultProps = { - value: new Date(), + value: [ new Date() ], format: 'MMMM Do', autoOk: false, minDate: '1900-01-01', diff --git a/lib/src/DatePicker/YearSelection.jsx b/lib/src/DatePicker/YearSelection.jsx index 5b878a2f6..6a1b1e035 100644 --- a/lib/src/DatePicker/YearSelection.jsx +++ b/lib/src/DatePicker/YearSelection.jsx @@ -8,7 +8,7 @@ import Year from './Year'; export class YearSelection extends PureComponent { static propTypes = { - date: PropTypes.shape({}).isRequired, + date: DomainPropTypes.dateRange.isRequired, minDate: DomainPropTypes.date.isRequired, maxDate: DomainPropTypes.date.isRequired, classes: PropTypes.object.isRequired, @@ -30,7 +30,7 @@ export class YearSelection extends PureComponent { onYearSelect = (year) => { const { date, onChange, utils } = this.props; - const newDate = utils.setYear(date, year); + const newDate = date.map(o => utils.setYear(o, year)); onChange(newDate); } @@ -55,7 +55,7 @@ export class YearSelection extends PureComponent { const { minDate, maxDate, date, classes, disablePast, disableFuture, utils, } = this.props; - const currentYear = utils.getYear(date); + const currentYear = utils.getYear(date[date.length - 1]); return (
diff --git a/lib/src/_shared/BasePicker.jsx b/lib/src/_shared/BasePicker.jsx index 8d27efb69..7b461a756 100644 --- a/lib/src/_shared/BasePicker.jsx +++ b/lib/src/_shared/BasePicker.jsx @@ -7,10 +7,10 @@ import withState from 'recompose/withState'; import withUtils from '../_shared/WithUtils'; -const getValidDateOrCurrent = ({ utils, value }) => { - const date = utils.date(value); +const getValidDateOrCurrent = ({ utils, value, autoSelectToday }) => { + const date = utils.ensureArray(value).map(utils.date); - return utils.isValid(date) && value !== null ? date : utils.date(); + return date.every(utils.isValid) && value.length !== 0 ? date : [ utils.date() ]; }; export const BasePickerHoc = compose( @@ -20,7 +20,7 @@ export const BasePickerHoc = compose( withState('isAccepted', 'handleAcceptedChange', false), lifecycle({ componentDidUpdate(prevProps) { - if (prevProps.value !== this.props.value) { + if (!this.props.utils.isEqual(prevProps.value, this.props.value)) { this.props.changeDate(getValidDateOrCurrent(this.props)); } }, @@ -28,7 +28,7 @@ export const BasePickerHoc = compose( withHandlers({ handleClear: ({ onChange }) => () => onChange(null), handleAccept: ({ onChange, date }) => () => onChange(date), - handleSetTodayDate: ({ changeDate, utils }) => () => changeDate(utils.date()), + handleSetTodayDate: ({ changeDate, utils }) => () => changeDate([ utils.date() ]), handleTextFieldChange: ({ changeDate, onChange }) => (date) => { if (date === null) { this.handleClear(); diff --git a/lib/src/_shared/DateTextField.jsx b/lib/src/_shared/DateTextField.jsx index a7d8e526f..7f76c88d4 100644 --- a/lib/src/_shared/DateTextField.jsx +++ b/lib/src/_shared/DateTextField.jsx @@ -12,11 +12,12 @@ import withUtils from '../_shared/WithUtils'; const getDisplayDate = (props) => { const { - utils, value, format, invalidLabel, emptyLabel, labelFunc, + utils, format, invalidLabel, emptyLabel, labelFunc, } = props; + const value = utils.ensureArray(props.value); - const isEmpty = value === null; - const date = utils.date(value); + const isEmpty = value.length < 1; + const date = value.map(utils.date); if (labelFunc) { return labelFunc(isEmpty ? null : date, invalidLabel); @@ -26,8 +27,8 @@ const getDisplayDate = (props) => { return emptyLabel; } - return utils.isValid(date) - ? utils.format(date, format) + return date.every(utils.isValid) + ? date.map(o => utils.format(o, format)).join(', ') : invalidLabel; }; @@ -43,25 +44,27 @@ const getError = (value, props) => { invalidDateMessage, } = props; - if (!utils.isValid(value)) { + if (!value.every(utils.isValid)) { // if null - do not show error - if (utils.isNull(value)) { + if (value.every(utils.isNull)) { return ''; } return invalidDateMessage; } + let endOfDay = utils.endOfDay(utils.date()) if ( - (maxDate && utils.isAfter(value, maxDate)) || - (disableFuture && utils.isAfter(value, utils.endOfDay(utils.date()))) + (maxDate && value.some(o => utils.isAfter(o, maxDate))) || + (disableFuture && value.some(o => utils.isAfter(o, endOfDay))) ) { return maxDateMessage; } + let startOfDay = utils.startOfDay(utils.date()) if ( - (minDate && utils.isBefore(value, minDate)) || - (disablePast && utils.isBefore(value, utils.startOfDay(utils.date()))) + (minDate && value.some(o => utils.isBefore(o, minDate))) || + (disablePast && value.some(o => utils.isBefore(o, startOfDay))) ) { return minDateMessage; } @@ -71,19 +74,14 @@ const getError = (value, props) => { export class DateTextField extends PureComponent { static updateState = props => ({ - value: props.value, + value: props.utils.ensureArray(props.value), displayValue: getDisplayDate(props), - error: getError(props.utils.date(props.value), props), + error: getError(props.utils.ensureArray(props.value).map(props.utils.date), props), }); static propTypes = { classes: PropTypes.shape({}).isRequired, - value: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.string, - PropTypes.number, - PropTypes.instanceOf(Date), - ]), + value: DomainPropTypes.dateRange, minDate: DomainPropTypes.date, maxDate: DomainPropTypes.date, disablePast: PropTypes.bool, @@ -131,7 +129,7 @@ export class DateTextField extends PureComponent { disabled: false, invalidLabel: 'Unknown', emptyLabel: '', - value: new Date(), + value: [ new Date() ], labelFunc: undefined, format: undefined, InputProps: undefined, @@ -190,8 +188,8 @@ export class DateTextField extends PureComponent { return; } - const oldValue = utils.date(this.state.value); - const newValue = utils.parse(value, format); + const oldValue = this.state.value.map(utils.date); + const newValue = value.split(', ').map(o => utils.parse(o, format)); const error = getError(newValue, this.props); this.setState({ @@ -200,11 +198,11 @@ export class DateTextField extends PureComponent { value: error ? newValue : oldValue, }, () => { if (!error && !utils.isEqual(newValue, oldValue)) { - this.props.onChange(newValue); + this.props.onChange(newValue.length > 1 ? newValue : newValue[0]); } if (error && onError) { - onError(newValue, error); + onError(newValue.length > 1 ? newValue : newValue[0], error); } }); } @@ -223,7 +221,7 @@ export class DateTextField extends PureComponent { handleChange = (e) => { const { utils, format } = this.props; - const parsedValue = utils.parse(e.target.value, format); + const parsedValue = e.target.value.split(', ').map(o => utils.parse(o, format)); this.setState({ displayValue: e.target.value, diff --git a/lib/src/constants/prop-types.js b/lib/src/constants/prop-types.js index 62f94687e..82e4da03e 100644 --- a/lib/src/constants/prop-types.js +++ b/lib/src/constants/prop-types.js @@ -7,7 +7,11 @@ const date = PropTypes.oneOfType([ PropTypes.instanceOf(Date), ]); -export default { +const dateRange = PropTypes.oneOfType([ date, -}; + PropTypes.arrayOf(date), +]); +export default { + date, dateRange +}; diff --git a/lib/src/utils/date-fns-utils.js b/lib/src/utils/date-fns-utils.js index e56146ed7..af610a38d 100644 --- a/lib/src/utils/date-fns-utils.js +++ b/lib/src/utils/date-fns-utils.js @@ -60,9 +60,21 @@ export default class DateFnsUtils { return true; } + if (Array.isArray(date) || Array.isArray(comparing)) { + const dateArray = this.ensureArray(date); + const comparingArray = this.ensureArray(comparing); + + return dateArray.length == comparingArray.length && + dateArray.every((o, i) => isEqual(o, comparingArray[i])); + } + return isEqual(date, comparing); } + ensureArray (value) { + return Array.isArray(value) ? value : [ value ]; + } + addDays = addDays isValid = isValid diff --git a/lib/src/utils/luxon-utils.js b/lib/src/utils/luxon-utils.js index feb2c9ccb..d69bc9eaf 100644 --- a/lib/src/utils/luxon-utils.js +++ b/lib/src/utils/luxon-utils.js @@ -40,9 +40,21 @@ export default class LuxonUtils { return true; } + if (Array.isArray(date) || Array.isArray(comparing)) { + const dateArray = this.ensureArray(date); + const comparingArray = this.ensureArray(comparing); + + return dateArray.length == comparingArray.length && + dateArray.every((o, i) => this.isEqual(o, comparingArray[i])); + } + return value === comparing; } + ensureArray (value) { + return Array.isArray(value) ? value : [ value ]; + } + isSameDay(value, comparing) { return value.hasSame(comparing, 'day'); } diff --git a/lib/src/utils/moment-utils.js b/lib/src/utils/moment-utils.js index 1c50f8362..6770c4b2e 100644 --- a/lib/src/utils/moment-utils.js +++ b/lib/src/utils/moment-utils.js @@ -138,9 +138,21 @@ export default class MomentUtils { return true; } + if (Array.isArray(date) || Array.isArray(comparing)) { + const dateArray = this.ensureArray(date); + const comparingArray = this.ensureArray(comparing); + + return dateArray.length == comparingArray.length && + dateArray.every((o, i) => this.isEqual(o, comparingArray[i])); + } + return this.moment(value).isSame(comparing); } + ensureArray (value) { + return Array.isArray(value) ? value : [ value ]; + } + getWeekArray(date) { const start = date.clone().startOf('month').startOf('week'); const end = date.clone().endOf('month').endOf('week'); diff --git a/lib/src/wrappers/ModalWrapper.jsx b/lib/src/wrappers/ModalWrapper.jsx index 199e02c9d..28ebb9a11 100644 --- a/lib/src/wrappers/ModalWrapper.jsx +++ b/lib/src/wrappers/ModalWrapper.jsx @@ -8,7 +8,7 @@ import DomainPropTypes from '../constants/prop-types'; export default class ModalWrapper extends PureComponent { static propTypes = { /** Picker value */ - value: DomainPropTypes.date, + value: DomainPropTypes.dateRange, /** Format string */ invalidLabel: PropTypes.node, /** Function for dynamic rendering label (date, invalidLabel) => string */ @@ -47,7 +47,7 @@ export default class ModalWrapper extends PureComponent { static defaultProps = { dialogContentClassName: '', invalidLabel: undefined, - value: new Date(), + value: [ new Date() ], labelFunc: undefined, okLabel: 'OK', cancelLabel: 'Cancel', From 322e8c4d9b6f0493ceb28a2ef4b1586feeae3a8b Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 12 Jul 2018 15:28:39 -0700 Subject: [PATCH 2/8] Multi select mode --- lib/src/DatePicker/Calendar.jsx | 17 +++++++++++++++-- lib/src/DatePicker/DatePicker.jsx | 4 ++++ lib/src/DatePicker/DatePickerWrapper.jsx | 5 +++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/src/DatePicker/Calendar.jsx b/lib/src/DatePicker/Calendar.jsx index 6577ab6ca..b1f04bd5c 100644 --- a/lib/src/DatePicker/Calendar.jsx +++ b/lib/src/DatePicker/Calendar.jsx @@ -28,6 +28,7 @@ export class Calendar extends Component { shouldDisableDate: PropTypes.func, utils: PropTypes.object.isRequired, allowKeyboardControl: PropTypes.bool, + multi: PropTypes.bool, }; static defaultProps = { @@ -40,6 +41,7 @@ export class Calendar extends Component { renderDay: undefined, allowKeyboardControl: false, shouldDisableDate: () => false, + multi: false, }; state = { @@ -77,12 +79,23 @@ export class Calendar extends Component { } onDateSelect = (day, isFinish = true) => { - const { date, utils } = this.props; + const { date, utils, multi } = this.props; const withHours = utils.setHours(day, utils.getHours(date[0])); const withMinutes = utils.setMinutes(withHours, utils.getMinutes(date[0])); + let finalDates = [ withMinutes ] + + if (multi) { + let i = date.findIndex(o => utils.isEqual(o, day)) + if (i === -1) { + finalDates = date.concat(withMinutes) + } else { + finalDates = [ ...date ] + finalDates.splice(i, 1) + } + } - this.props.onChange([ withMinutes ], isFinish); + this.props.onChange(finalDates, isFinish); }; handleChangeMonth = (newMonth) => { diff --git a/lib/src/DatePicker/DatePicker.jsx b/lib/src/DatePicker/DatePicker.jsx index d960e2290..91ce18301 100644 --- a/lib/src/DatePicker/DatePicker.jsx +++ b/lib/src/DatePicker/DatePicker.jsx @@ -25,6 +25,7 @@ export class DatePicker extends PureComponent { utils: PropTypes.object.isRequired, shouldDisableDate: PropTypes.func, allowKeyboardControl: PropTypes.bool, + multi: PropTypes.bool, } static defaultProps = { @@ -40,6 +41,7 @@ export class DatePicker extends PureComponent { rightArrowIcon: undefined, renderDay: undefined, shouldDisableDate: undefined, + multi: false, } state = { @@ -83,6 +85,7 @@ export class DatePicker extends PureComponent { utils, shouldDisableDate, allowKeyboardControl, + multi, } = this.props; const { showYearSelection } = this.state; @@ -133,6 +136,7 @@ export class DatePicker extends PureComponent { utils={utils} shouldDisableDate={shouldDisableDate} allowKeyboardControl={allowKeyboardControl} + multi={multi} /> } diff --git a/lib/src/DatePicker/DatePickerWrapper.jsx b/lib/src/DatePicker/DatePickerWrapper.jsx index c593a1b9d..4c0547c7c 100644 --- a/lib/src/DatePicker/DatePickerWrapper.jsx +++ b/lib/src/DatePicker/DatePickerWrapper.jsx @@ -25,6 +25,7 @@ export const DatePickerWrapper = (props) => { rightArrowIcon, shouldDisableDate, value, + multi, ...other } = props; @@ -72,6 +73,7 @@ export const DatePickerWrapper = (props) => { renderDay={renderDay} rightArrowIcon={rightArrowIcon} shouldDisableDate={shouldDisableDate} + multi={multi} /> ) @@ -114,6 +116,8 @@ DatePickerWrapper.propTypes = { /** Enables keyboard listener for moving between days in calendar */ allowKeyboardControl: PropTypes.bool, forwardedRef: PropTypes.func, + /** Enables selecting multiple dates **/ + multi: PropTypes.bool, }; DatePickerWrapper.defaultProps = { @@ -133,6 +137,7 @@ DatePickerWrapper.defaultProps = { labelFunc: undefined, shouldDisableDate: undefined, forwardedRef: undefined, + multi: false, }; export default React.forwardRef((props, ref) => ( From 2af7c26afea760ea620e32cdd2c52093e5fa4321 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 12 Jul 2018 16:03:36 -0700 Subject: [PATCH 3/8] Range select mode --- lib/src/DatePicker/Calendar.jsx | 42 +++++++++++++++++++----- lib/src/DatePicker/DatePicker.jsx | 4 +++ lib/src/DatePicker/DatePickerWrapper.jsx | 5 +++ lib/src/DatePicker/Day.jsx | 37 +++++++++++++++++++-- lib/src/utils/date-fns-utils.js | 4 +++ lib/src/utils/luxon-utils.js | 4 +++ lib/src/utils/moment-utils.js | 4 +++ 7 files changed, 88 insertions(+), 12 deletions(-) diff --git a/lib/src/DatePicker/Calendar.jsx b/lib/src/DatePicker/Calendar.jsx index b1f04bd5c..fa255d765 100644 --- a/lib/src/DatePicker/Calendar.jsx +++ b/lib/src/DatePicker/Calendar.jsx @@ -29,6 +29,7 @@ export class Calendar extends Component { utils: PropTypes.object.isRequired, allowKeyboardControl: PropTypes.bool, multi: PropTypes.bool, + range: PropTypes.bool, }; static defaultProps = { @@ -42,6 +43,7 @@ export class Calendar extends Component { allowKeyboardControl: false, shouldDisableDate: () => false, multi: false, + range: false, }; state = { @@ -79,23 +81,31 @@ export class Calendar extends Component { } onDateSelect = (day, isFinish = true) => { - const { date, utils, multi } = this.props; + const { date, utils, multi, range } = this.props; - const withHours = utils.setHours(day, utils.getHours(date[0])); - const withMinutes = utils.setMinutes(withHours, utils.getMinutes(date[0])); - let finalDates = [ withMinutes ] + let newDate = day + if (date.length > 0) { + newDate = utils.setHours(day, utils.getHours(date[0])); + newDate = utils.setMinutes(newDate, utils.getMinutes(date[0])); + } if (multi) { let i = date.findIndex(o => utils.isEqual(o, day)) if (i === -1) { - finalDates = date.concat(withMinutes) + newDate = date.concat(newDate) } else { - finalDates = [ ...date ] - finalDates.splice(i, 1) + newDate = [ ...date ] + newDate.splice(i, 1) } + } else if (range && date.length === 1) { + newDate = utils.isAfter(newDate, date[0]) + ? [ date[0], newDate ] + : [ newDate, date[0] ]; + } else { + newDate = [ newDate ] } - this.props.onChange(finalDates, isFinish); + this.props.onChange(newDate, isFinish); }; handleChangeMonth = (newMonth) => { @@ -197,7 +207,8 @@ export class Calendar extends Component { } renderDays = (week) => { - const { date, renderDay, utils } = this.props; + const { date, renderDay, utils, range } = this.props; + const { hover } = this.state; const selectedDate = date.map(utils.startOfDay); const currentMonthNumber = utils.getMonth(this.state.currentMonth); @@ -207,12 +218,24 @@ export class Calendar extends Component { const disabled = this.shouldDisableDate(day); const dayInCurrentMonth = utils.getMonth(day) === currentMonthNumber; + let additionalProps; + + if (range) { + additionalProps = { + prelighted: date.length === 1 && utils.isBetween(day, date[0], hover), + highlighted: date.length > 1 && utils.isBetween(day, date[0], date[1]), + leftCap: utils.isEqual(day, Math.min(date[0], date[1] || hover)), + rightCap: utils.isEqual(day, Math.max(date[0], date[1] || hover)), + } + } + let dayComponent = ( @@ -229,6 +252,7 @@ export class Calendar extends Component { dayInCurrentMonth={dayInCurrentMonth} disabled={disabled} onSelect={this.onDateSelect} + onMouseEnter={e => this.setState({ hover: day })} > {dayComponent} diff --git a/lib/src/DatePicker/DatePicker.jsx b/lib/src/DatePicker/DatePicker.jsx index 91ce18301..5569000dc 100644 --- a/lib/src/DatePicker/DatePicker.jsx +++ b/lib/src/DatePicker/DatePicker.jsx @@ -26,6 +26,7 @@ export class DatePicker extends PureComponent { shouldDisableDate: PropTypes.func, allowKeyboardControl: PropTypes.bool, multi: PropTypes.bool, + range: PropTypes.bool, } static defaultProps = { @@ -42,6 +43,7 @@ export class DatePicker extends PureComponent { renderDay: undefined, shouldDisableDate: undefined, multi: false, + range: false, } state = { @@ -86,6 +88,7 @@ export class DatePicker extends PureComponent { shouldDisableDate, allowKeyboardControl, multi, + range, } = this.props; const { showYearSelection } = this.state; @@ -137,6 +140,7 @@ export class DatePicker extends PureComponent { shouldDisableDate={shouldDisableDate} allowKeyboardControl={allowKeyboardControl} multi={multi} + range={range} /> } diff --git a/lib/src/DatePicker/DatePickerWrapper.jsx b/lib/src/DatePicker/DatePickerWrapper.jsx index 4c0547c7c..0b7bfea3d 100644 --- a/lib/src/DatePicker/DatePickerWrapper.jsx +++ b/lib/src/DatePicker/DatePickerWrapper.jsx @@ -26,6 +26,7 @@ export const DatePickerWrapper = (props) => { shouldDisableDate, value, multi, + range, ...other } = props; @@ -74,6 +75,7 @@ export const DatePickerWrapper = (props) => { rightArrowIcon={rightArrowIcon} shouldDisableDate={shouldDisableDate} multi={multi} + range={range} /> ) @@ -118,6 +120,8 @@ DatePickerWrapper.propTypes = { forwardedRef: PropTypes.func, /** Enables selecting multiple dates **/ multi: PropTypes.bool, + /** Enables selecting a range of dates **/ + range: PropTypes.bool, }; DatePickerWrapper.defaultProps = { @@ -138,6 +142,7 @@ DatePickerWrapper.defaultProps = { shouldDisableDate: undefined, forwardedRef: undefined, multi: false, + range: false, }; export default React.forwardRef((props, ref) => ( diff --git a/lib/src/DatePicker/Day.jsx b/lib/src/DatePicker/Day.jsx index 0c3f033d6..eb536e4db 100644 --- a/lib/src/DatePicker/Day.jsx +++ b/lib/src/DatePicker/Day.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; import withStyles from '@material-ui/core/styles/withStyles'; import IconButton from '@material-ui/core/IconButton'; +import { fade } from '@material-ui/core/styles/colorManipulator'; class Day extends PureComponent { static propTypes = { @@ -23,17 +24,18 @@ class Day extends PureComponent { render() { const { - children, classes, disabled, hidden, current, selected, ...other + children, classes, disabled, hidden, current, selected, + prelighted, highlighted, leftCap, rightCap, ...other } = this.props; const className = classnames(classes.day, { [classes.hidden]: hidden, [classes.current]: current, - [classes.selected]: selected, + [classes.selected]: selected || highlighted, [classes.disabled]: disabled, }); - return ( + const icon = ( {children} ); + + if (highlighted || prelighted) { + return ( +
+ {icon} +
+ ); + } else { + return icon; + } } } @@ -74,6 +91,20 @@ const styles = theme => ({ pointerEvents: 'none', color: theme.palette.text.hint, }, + prelighted: { + backgroundColor: fade(theme.palette.action.active, theme.palette.action.hoverOpacity), + }, + highlighted: { + backgroundColor: theme.palette.primary.main, + }, + leftCap: { + borderTopLeftRadius: '50%', + borderBottomLeftRadius: '50%', + }, + rightCap: { + borderTopRightRadius: '50%', + borderBottomRightRadius: '50%', + }, }); export default withStyles(styles, { name: 'MuiPickersDay' })(Day); diff --git a/lib/src/utils/date-fns-utils.js b/lib/src/utils/date-fns-utils.js index af610a38d..cce9a8164 100644 --- a/lib/src/utils/date-fns-utils.js +++ b/lib/src/utils/date-fns-utils.js @@ -89,6 +89,10 @@ export default class DateFnsUtils { isBefore = isBefore + isBetween (a, b, c) { + return a >= Math.min(b, c) && a <= Math.max(b, c) + } + isAfterDay(date, value) { return isAfter(date, endOfDay(value)); } diff --git a/lib/src/utils/luxon-utils.js b/lib/src/utils/luxon-utils.js index d69bc9eaf..e4d2c7abc 100644 --- a/lib/src/utils/luxon-utils.js +++ b/lib/src/utils/luxon-utils.js @@ -63,6 +63,10 @@ export default class LuxonUtils { return value > comparing; } + isBetween (a, b, c) { + return a >= Math.min(b, c) && a <= Math.max(b, c) + } + isAfterDay(value, comparing) { const diff = value.diff(comparing, 'days').toObject(); return diff.days > 0; diff --git a/lib/src/utils/moment-utils.js b/lib/src/utils/moment-utils.js index 6770c4b2e..7c1a4ae46 100644 --- a/lib/src/utils/moment-utils.js +++ b/lib/src/utils/moment-utils.js @@ -31,6 +31,10 @@ export default class MomentUtils { return date.isAfter(value); } + isBetween (a, b, c) { + return a >= Math.min(b, c) && a <= Math.max(b, c) + } + isBefore(date, value) { return date.isBefore(value); } From d5152bb9c0bd01c761c57548e2af1d220361d181 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 13 Jul 2018 12:11:18 -0700 Subject: [PATCH 4/8] Optionally don't automatically select today's date --- lib/src/DatePicker/Calendar.jsx | 6 ++++-- lib/src/DatePicker/DatePickerWrapper.jsx | 6 +++++- lib/src/_shared/BasePicker.jsx | 7 ++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/src/DatePicker/Calendar.jsx b/lib/src/DatePicker/Calendar.jsx index fa255d765..9df6ff9a7 100644 --- a/lib/src/DatePicker/Calendar.jsx +++ b/lib/src/DatePicker/Calendar.jsx @@ -47,14 +47,16 @@ export class Calendar extends Component { }; state = { - currentMonth: this.props.utils.getStartOfMonth(this.props.date[this.props.date.length - 1]), + currentMonth: this.props.utils.getStartOfMonth( + this.props.date.length > 0 ? this.props.date[this.props.date.length - 1] : this.props.utils.date()), }; static getDerivedStateFromProps(nextProps, state) { if (!nextProps.utils.isEqual(nextProps.date, state.lastDate)) { return { lastDate: nextProps.date, - currentMonth: nextProps.utils.getStartOfMonth(nextProps.date[nextProps.date.length - 1]), + currentMonth: nextProps.utils.getStartOfMonth( + nextProps.date.length > 0 ? nextProps.date[nextProps.date.length - 1] : nextProps.utils.date()), }; } diff --git a/lib/src/DatePicker/DatePickerWrapper.jsx b/lib/src/DatePicker/DatePickerWrapper.jsx index 0b7bfea3d..451f63165 100644 --- a/lib/src/DatePicker/DatePickerWrapper.jsx +++ b/lib/src/DatePicker/DatePickerWrapper.jsx @@ -27,11 +27,12 @@ export const DatePickerWrapper = (props) => { value, multi, range, + autoSelectToday, ...other } = props; return ( - + { ({ date, @@ -122,6 +123,8 @@ DatePickerWrapper.propTypes = { multi: PropTypes.bool, /** Enables selecting a range of dates **/ range: PropTypes.bool, + /** Automatically select today's date if value is invalid **/ + autoSelectToday: PropTypes.bool, }; DatePickerWrapper.defaultProps = { @@ -143,6 +146,7 @@ DatePickerWrapper.defaultProps = { forwardedRef: undefined, multi: false, range: false, + autoSelectToday: true, }; export default React.forwardRef((props, ref) => ( diff --git a/lib/src/_shared/BasePicker.jsx b/lib/src/_shared/BasePicker.jsx index 7b461a756..21b370e51 100644 --- a/lib/src/_shared/BasePicker.jsx +++ b/lib/src/_shared/BasePicker.jsx @@ -10,7 +10,12 @@ import withUtils from '../_shared/WithUtils'; const getValidDateOrCurrent = ({ utils, value, autoSelectToday }) => { const date = utils.ensureArray(value).map(utils.date); - return date.every(utils.isValid) && value.length !== 0 ? date : [ utils.date() ]; + if (date.every(utils.isValid) && date.length !== 0) + return date; + else if (autoSelectToday) + return [ utils.date() ]; + else + return []; }; export const BasePickerHoc = compose( From f73266ca3c2344a6e72241c604499d3046ba53fd Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 13 Jul 2018 10:19:12 -0700 Subject: [PATCH 5/8] Specify how to join multiple values using `formatSeperator` prop --- lib/src/DatePicker/DatePickerWrapper.jsx | 5 +++++ lib/src/_shared/DateTextField.jsx | 15 ++++++++++----- lib/src/wrappers/ModalWrapper.jsx | 4 ++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/src/DatePicker/DatePickerWrapper.jsx b/lib/src/DatePicker/DatePickerWrapper.jsx index 451f63165..e92cf4d77 100644 --- a/lib/src/DatePicker/DatePickerWrapper.jsx +++ b/lib/src/DatePicker/DatePickerWrapper.jsx @@ -28,6 +28,7 @@ export const DatePickerWrapper = (props) => { multi, range, autoSelectToday, + formatSeperator, ...other } = props; @@ -59,6 +60,7 @@ export const DatePickerWrapper = (props) => { ref={forwardedRef} value={value} isAccepted={isAccepted} + formatSeperator={formatSeperator} {...other} > ( diff --git a/lib/src/_shared/DateTextField.jsx b/lib/src/_shared/DateTextField.jsx index 7f76c88d4..04c7160ea 100644 --- a/lib/src/_shared/DateTextField.jsx +++ b/lib/src/_shared/DateTextField.jsx @@ -12,7 +12,7 @@ import withUtils from '../_shared/WithUtils'; const getDisplayDate = (props) => { const { - utils, format, invalidLabel, emptyLabel, labelFunc, + utils, format, invalidLabel, emptyLabel, labelFunc, formatSeperator } = props; const value = utils.ensureArray(props.value); @@ -28,7 +28,7 @@ const getDisplayDate = (props) => { } return date.every(utils.isValid) - ? date.map(o => utils.format(o, format)).join(', ') + ? date.map(o => utils.format(o, format)).join(formatSeperator) : invalidLabel; }; @@ -123,6 +123,8 @@ export class DateTextField extends PureComponent { adornmentPosition: PropTypes.oneOf(['start', 'end']), /** Callback firing when date that applied in the keyboard is invalid */ onError: PropTypes.func, + /** String to join multiple values **/ + formatSeperator: PropTypes.string, } static defaultProps = { @@ -151,6 +153,7 @@ export class DateTextField extends PureComponent { TextFieldComponent: TextField, InputAdornmentProps: {}, adornmentPosition: 'end', + formatSeperator: ', ', } state = DateTextField.updateState(this.props) @@ -176,6 +179,7 @@ export class DateTextField extends PureComponent { utils, format, onError, + formatSeperator, } = this.props; if (value === '') { @@ -189,7 +193,7 @@ export class DateTextField extends PureComponent { } const oldValue = this.state.value.map(utils.date); - const newValue = value.split(', ').map(o => utils.parse(o, format)); + const newValue = value.split(formatSeperator).map(o => utils.parse(o, format)); const error = getError(newValue, this.props); this.setState({ @@ -220,8 +224,8 @@ export class DateTextField extends PureComponent { }; handleChange = (e) => { - const { utils, format } = this.props; - const parsedValue = e.target.value.split(', ').map(o => utils.parse(o, format)); + const { utils, format, formatSeperator, } = this.props; + const parsedValue = e.target.value.split(formatSeperator).map(o => utils.parse(o, format)); this.setState({ displayValue: e.target.value, @@ -285,6 +289,7 @@ export class DateTextField extends PureComponent { TextFieldComponent, utils, value, + formatSeperator, ...other } = this.props; diff --git a/lib/src/wrappers/ModalWrapper.jsx b/lib/src/wrappers/ModalWrapper.jsx index 28ebb9a11..87ee46190 100644 --- a/lib/src/wrappers/ModalWrapper.jsx +++ b/lib/src/wrappers/ModalWrapper.jsx @@ -42,6 +42,7 @@ export default class ModalWrapper extends PureComponent { dialogContentClassName: PropTypes.string, isAccepted: PropTypes.bool.isRequired, container: PropTypes.node, + formatSeperator: PropTypes.string, } static defaultProps = { @@ -63,6 +64,7 @@ export default class ModalWrapper extends PureComponent { onClose: undefined, onSetToday: undefined, container: undefined, + formatSeperator: ', ', } state = { @@ -156,6 +158,7 @@ export default class ModalWrapper extends PureComponent { onSetToday, isAccepted, container, + formatSeperator, ...other } = this.props; @@ -169,6 +172,7 @@ export default class ModalWrapper extends PureComponent { invalidLabel={invalidLabel} labelFunc={labelFunc} clearable={clearable} + formatSeperator={formatSeperator} {...other} /> From 7fe47a9717b8875ccd1627e042922d0beaf99d1e Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 13 Jul 2018 12:37:37 -0700 Subject: [PATCH 6/8] Unwrap arrays before passing to time components --- lib/src/DateTimePicker/DateTimePicker.jsx | 10 +++++----- lib/src/TimePicker/TimePickerWrapper.jsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/DateTimePicker/DateTimePicker.jsx b/lib/src/DateTimePicker/DateTimePicker.jsx index cfbe69f1a..4a9116d93 100644 --- a/lib/src/DateTimePicker/DateTimePicker.jsx +++ b/lib/src/DateTimePicker/DateTimePicker.jsx @@ -22,7 +22,7 @@ export class DateTimePicker extends Component { animateYearScrolling: PropTypes.bool, autoSubmit: PropTypes.bool, classes: PropTypes.object.isRequired, - date: PropTypes.object.isRequired, + date: DomainPropTypes.dateRange.isRequired, dateRangeIcon: PropTypes.node, disableFuture: PropTypes.bool, disablePast: PropTypes.bool, @@ -128,7 +128,7 @@ export class DateTimePicker extends Component { return ( diff --git a/lib/src/TimePicker/TimePickerWrapper.jsx b/lib/src/TimePicker/TimePickerWrapper.jsx index 85bcb87f2..fc513810c 100644 --- a/lib/src/TimePicker/TimePickerWrapper.jsx +++ b/lib/src/TimePicker/TimePickerWrapper.jsx @@ -38,7 +38,7 @@ export const TimePickerWrapper = (props) => { {...other} > Date: Mon, 23 Jul 2018 16:51:56 -0700 Subject: [PATCH 7/8] Show empty input with single null value --- lib/src/_shared/DateTextField.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/src/_shared/DateTextField.jsx b/lib/src/_shared/DateTextField.jsx index dd59c0157..b162f3f67 100644 --- a/lib/src/_shared/DateTextField.jsx +++ b/lib/src/_shared/DateTextField.jsx @@ -16,17 +16,18 @@ const getDisplayDate = (props) => { } = props; const value = utils.ensureArray(props.value); - const isEmpty = value.length < 1; - const date = value.map(utils.date); + const isEmpty = value == null || value.length < 1 || (value.length == 1 && value[0] == null); - if (labelFunc) { - return labelFunc(isEmpty ? null : date, invalidLabel); - } + const date = value.map(utils.date); if (isEmpty) { return emptyLabel; } + if (labelFunc) { + return labelFunc(date, invalidLabel); + } + return date.every(utils.isValid) ? date.map(o => utils.format(o, format)).join(formatSeperator) : invalidLabel; From 041d481d0f5630d001571495a0b94d408122ded6 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 23 Jul 2018 16:58:02 -0700 Subject: [PATCH 8/8] Remove autoSelectToday prop in lieu of new initialFocusedDate prop --- lib/src/DatePicker/DatePickerWrapper.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/src/DatePicker/DatePickerWrapper.jsx b/lib/src/DatePicker/DatePickerWrapper.jsx index fffdd2397..8f867db56 100644 --- a/lib/src/DatePicker/DatePickerWrapper.jsx +++ b/lib/src/DatePicker/DatePickerWrapper.jsx @@ -28,13 +28,12 @@ export const DatePickerWrapper = (props) => { value, multi, range, - autoSelectToday, formatSeperator, ...other } = props; return ( - + { ({ date, @@ -128,8 +127,6 @@ DatePickerWrapper.propTypes = { multi: PropTypes.bool, /** Enables selecting a range of dates **/ range: PropTypes.bool, - /** Automatically select today's date if value is invalid **/ - autoSelectToday: PropTypes.bool, /** String to join multiple values **/ formatSeperator: PropTypes.string, }; @@ -154,7 +151,6 @@ DatePickerWrapper.defaultProps = { forwardedRef: undefined, multi: false, range: false, - autoSelectToday: true, formatSeperator: ', ', };