{
renderDay={renderDay}
rightArrowIcon={rightArrowIcon}
shouldDisableDate={shouldDisableDate}
+ multi={multi}
+ range={range}
/>
)
@@ -83,7 +89,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,10 +123,16 @@ DatePickerWrapper.propTypes = {
/** Enables keyboard listener for moving between days in calendar */
allowKeyboardControl: PropTypes.bool,
forwardedRef: PropTypes.func,
+ /** Enables selecting multiple dates **/
+ multi: PropTypes.bool,
+ /** Enables selecting a range of dates **/
+ range: PropTypes.bool,
+ /** String to join multiple values **/
+ formatSeperator: PropTypes.string,
};
DatePickerWrapper.defaultProps = {
- value: new Date(),
+ value: [ new Date() ],
format: 'MMMM Do',
autoOk: false,
minDate: '1900-01-01',
@@ -137,6 +149,9 @@ DatePickerWrapper.defaultProps = {
labelFunc: undefined,
shouldDisableDate: undefined,
forwardedRef: undefined,
+ multi: false,
+ range: false,
+ formatSeperator: ', ',
};
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/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/DateTimePicker/DateTimePicker.jsx b/lib/src/DateTimePicker/DateTimePicker.jsx
index adf7622ea..6c479ddeb 100644
--- a/lib/src/DateTimePicker/DateTimePicker.jsx
+++ b/lib/src/DateTimePicker/DateTimePicker.jsx
@@ -21,7 +21,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,
@@ -125,7 +125,7 @@ export class DateTimePicker extends Component {
return (
{
{...other}
>
{
- const initialDate = value || initialFocusedDate || utils.date();
- const date = utils.date(initialDate);
+ const date = utils.ensureArray(value || initialFocusedDate).map(utils.date);
- return utils.isValid(date) ? date : utils.date();
+ if (date.every(utils.isValid) && date.length !== 0)
+ return date;
+ else if (initialFocusedDate === false)
+ return [];
+ else
+ return [ utils.date() ];
};
export const BasePickerHoc = compose(
@@ -21,7 +25,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(getInitialDate(this.props));
}
},
@@ -29,7 +33,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) {
onChange(null);
@@ -62,4 +66,3 @@ export const BasePickerHoc = compose(
);
export default withRenderProps(BasePickerHoc);
-
diff --git a/lib/src/_shared/DateTextField.jsx b/lib/src/_shared/DateTextField.jsx
index 4e19ba575..b162f3f67 100644
--- a/lib/src/_shared/DateTextField.jsx
+++ b/lib/src/_shared/DateTextField.jsx
@@ -12,22 +12,24 @@ import withUtils from '../_shared/WithUtils';
const getDisplayDate = (props) => {
const {
- utils, value, format, invalidLabel, emptyLabel, labelFunc,
+ utils, format, invalidLabel, emptyLabel, labelFunc, formatSeperator
} = props;
+ const value = utils.ensureArray(props.value);
- const isEmpty = value === null;
- const date = utils.date(value);
+ 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;
}
- return utils.isValid(date)
- ? utils.format(date, format)
+ if (labelFunc) {
+ return labelFunc(date, invalidLabel);
+ }
+
+ return date.every(utils.isValid)
+ ? date.map(o => utils.format(o, format)).join(formatSeperator)
: invalidLabel;
};
@@ -43,25 +45,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 +75,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,
@@ -125,6 +124,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,
/** Callback firing on change input in keyboard mode */
onInputChange: PropTypes.func,
}
@@ -133,7 +134,7 @@ export class DateTextField extends PureComponent {
disabled: false,
invalidLabel: 'Unknown',
emptyLabel: '',
- value: new Date(),
+ value: [ new Date() ],
labelFunc: undefined,
format: undefined,
InputProps: undefined,
@@ -156,6 +157,7 @@ export class DateTextField extends PureComponent {
TextFieldComponent: TextField,
InputAdornmentProps: {},
adornmentPosition: 'end',
+ formatSeperator: ', ',
}
state = DateTextField.updateState(this.props)
@@ -181,6 +183,7 @@ export class DateTextField extends PureComponent {
utils,
format,
onError,
+ formatSeperator,
} = this.props;
if (value === '') {
@@ -193,8 +196,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(formatSeperator).map(o => utils.parse(o, format));
const error = getError(newValue, this.props);
this.setState({
@@ -203,11 +206,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);
}
});
}
@@ -225,8 +228,8 @@ export class DateTextField extends PureComponent {
};
handleChange = (e) => {
- const { utils, format, onInputChange } = this.props;
- const parsedValue = utils.parse(e.target.value, format);
+ const { utils, format, formatSeperator, onInputChange, } = this.props;
+ const parsedValue = e.target.value.split(formatSeperator).map(o => utils.parse(o, format));
if (onInputChange) {
onInputChange(e);
@@ -294,6 +297,7 @@ export class DateTextField extends PureComponent {
TextFieldComponent,
utils,
value,
+ formatSeperator,
onInputChange,
...other
} = this.props;
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 b21bb7c20..d7a7814d2 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
@@ -77,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 c3963eec3..60dc0bfbe 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');
}
@@ -51,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 9d8c206b4..2c5ea3ac1 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);
}
@@ -142,9 +146,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..87ee46190 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 */
@@ -42,12 +42,13 @@ export default class ModalWrapper extends PureComponent {
dialogContentClassName: PropTypes.string,
isAccepted: PropTypes.bool.isRequired,
container: PropTypes.node,
+ formatSeperator: PropTypes.string,
}
static defaultProps = {
dialogContentClassName: '',
invalidLabel: undefined,
- value: new Date(),
+ value: [ new Date() ],
labelFunc: undefined,
okLabel: 'OK',
cancelLabel: 'Cancel',
@@ -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}
/>