react-date-range-headless
Version:
A React component for choosing dates and date ranges. A fork of hypeserver/react-date-range in which the Headless UI Listbox (Select) uses instead html select and options
237 lines (223 loc) • 7.15 kB
JavaScript
/* eslint-disable no-fallthrough */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { startOfDay, format, isSameDay, isAfter, isBefore, endOfDay } from 'date-fns';
class DayCell extends Component {
constructor(props, context) {
super(props, context);
this.state = {
hover: false,
active: false,
};
}
handleKeyEvent = event => {
const { day, onMouseDown, onMouseUp } = this.props;
if ([13 /* space */, 32 /* enter */].includes(event.keyCode)) {
if (event.type === 'keydown') onMouseDown(day);
else onMouseUp(day);
}
};
handleMouseEvent = event => {
const {
day,
disabled,
onPreviewChange,
onMouseEnter,
onMouseDown,
onMouseUp,
onHoverDate,
} = this.props;
const stateChanges = {};
if (disabled) {
onPreviewChange();
return;
}
switch (event.type) {
case 'mouseenter':
onMouseEnter(day);
onPreviewChange(day);
onHoverDate(day);
stateChanges.hover = true;
break;
case 'blur':
case 'mouseleave':
stateChanges.hover = false;
onHoverDate(null);
break;
case 'mousedown':
stateChanges.active = true;
onMouseDown(day);
break;
case 'mouseup':
event.stopPropagation();
stateChanges.active = false;
onMouseUp(day);
break;
case 'focus':
onPreviewChange(day);
break;
}
if (Object.keys(stateChanges).length) {
this.setState(stateChanges);
}
};
getClassNames = () => {
const {
isPassive,
isToday,
isWeekend,
isStartOfWeek,
isEndOfWeek,
isStartOfMonth,
isEndOfMonth,
disabled,
styles,
} = this.props;
return classnames(styles.day, {
[styles.dayPassive]: isPassive,
[styles.dayDisabled]: disabled,
[styles.dayToday]: isToday,
[styles.dayWeekend]: isWeekend,
[styles.dayStartOfWeek]: isStartOfWeek,
[styles.dayEndOfWeek]: isEndOfWeek,
[styles.dayStartOfMonth]: isStartOfMonth,
[styles.dayEndOfMonth]: isEndOfMonth,
[styles.dayHovered]: this.state.hover,
[styles.dayActive]: this.state.active,
});
};
renderPreviewPlaceholder = () => {
const { preview, day, styles } = this.props;
if (!preview) return null;
const startDate = preview.startDate ? endOfDay(preview.startDate) : null;
const endDate = preview.endDate ? startOfDay(preview.endDate) : null;
const isInRange =
(!startDate || isAfter(day, startDate)) && (!endDate || isBefore(day, endDate));
const isStartEdge = !isInRange && isSameDay(day, startDate);
const isEndEdge = !isInRange && isSameDay(day, endDate);
const isDaySecondLessFirst = endDate < startDate;
return (
<span
className={classnames({
[styles.dayStartPreview]: isStartEdge,
[styles.dayInPreview]: isInRange,
[styles.dayEndPreview]: isEndEdge,
[styles.daySecondLessFirst]: isDaySecondLessFirst,
})}
style={{ color: preview.color }}
/>
);
};
renderSelectionPlaceholders = () => {
const { styles, ranges, day } = this.props;
if (this.props.displayMode === 'date') {
let isSelected = isSameDay(this.props.day, this.props.date);
return isSelected ? (
<span className={styles.selected} style={{ color: this.props.color }} />
) : null;
}
const inRanges = ranges.reduce((result, range) => {
let startDate = range.startDate;
let endDate = range.endDate;
if (startDate && endDate && isBefore(endDate, startDate)) {
[startDate, endDate] = [endDate, startDate];
}
startDate = startDate ? endOfDay(startDate) : null;
endDate = endDate ? startOfDay(endDate) : null;
const isInRange = isAfter(day, startDate) && isBefore(day, endDate);
const isStartEdge = !isInRange && isSameDay(day, startDate);
const isEndEdge = !isInRange && isSameDay(day, endDate);
if (isInRange || isStartEdge || isEndEdge) {
return [
...result,
{
isStartEdge,
isEndEdge: isEndEdge,
isInRange,
...range,
},
];
}
return result;
}, []);
return inRanges.map((range, i) => (
<span
key={i}
className={classnames({
[styles.startEdge]: range.isStartEdge,
[styles.endEdge]: range.isEndEdge,
[styles.inRange]: range.isInRange,
})}
style={{ color: range.color || this.props.color }}
/>
));
};
render() {
const { dayContentRenderer } = this.props;
return (
<button
type="button"
onMouseEnter={this.handleMouseEvent}
onMouseLeave={this.handleMouseEvent}
onFocus={this.handleMouseEvent}
onMouseDown={this.handleMouseEvent}
onMouseUp={this.handleMouseEvent}
onBlur={this.handleMouseEvent}
onPauseCapture={this.handleMouseEvent}
onKeyDown={this.handleKeyEvent}
onKeyUp={this.handleKeyEvent}
className={this.getClassNames(this.props.styles)}
{...(this.props.disabled || this.props.isPassive ? { tabIndex: -1 } : {})}
style={{ color: this.props.color }}>
{this.renderSelectionPlaceholders()}
{this.renderPreviewPlaceholder()}
<span className={this.props.styles.dayNumber}>
{dayContentRenderer?.(this.props.day) || (
<span>{format(this.props.day, this.props.dayDisplayFormat)}</span>
)}
</span>
</button>
);
}
}
DayCell.defaultProps = {};
export const rangeShape = PropTypes.shape({
startDate: PropTypes.object,
endDate: PropTypes.object,
color: PropTypes.string,
key: PropTypes.string,
autoFocus: PropTypes.bool,
disabled: PropTypes.bool,
showDateDisplay: PropTypes.bool,
});
DayCell.propTypes = {
day: PropTypes.object.isRequired,
dayDisplayFormat: PropTypes.string,
date: PropTypes.object,
ranges: PropTypes.arrayOf(rangeShape),
preview: PropTypes.shape({
startDate: PropTypes.object,
endDate: PropTypes.object,
color: PropTypes.string,
}),
onPreviewChange: PropTypes.func,
previewColor: PropTypes.string,
disabled: PropTypes.bool,
isPassive: PropTypes.bool,
isToday: PropTypes.bool,
isWeekend: PropTypes.bool,
isStartOfWeek: PropTypes.bool,
isEndOfWeek: PropTypes.bool,
isStartOfMonth: PropTypes.bool,
isEndOfMonth: PropTypes.bool,
color: PropTypes.string,
displayMode: PropTypes.oneOf(['dateRange', 'date']),
styles: PropTypes.object,
onMouseDown: PropTypes.func,
onMouseUp: PropTypes.func,
onMouseEnter: PropTypes.func,
dayContentRenderer: PropTypes.func,
onHoverDate: PropTypes.func,
};
export default DayCell;