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
143 lines (129 loc) • 4.64 kB
JavaScript
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styles from '../../styles';
import { defaultInputRanges, defaultStaticRanges } from '../../defaultRanges';
import { rangeShape } from '../DayCell';
import InputRangeField from '../InputRangeField';
import cx from 'classnames';
class DefinedRange extends Component {
constructor(props) {
super(props);
this.state = {
rangeOffset: 0,
focusedInput: -1,
};
}
handleRangeChange = range => {
const { onChange, ranges, focusedRange } = this.props;
const selectedRange = ranges[focusedRange[0]];
if (!onChange || !selectedRange) return;
onChange({
[selectedRange.key || `range${focusedRange[0] + 1}`]: { ...selectedRange, ...range },
});
};
getRangeOptionValue(option) {
const { ranges = [], focusedRange = [] } = this.props;
if (typeof option.getCurrentValue !== 'function') {
return '';
}
const selectedRange = ranges[focusedRange[0]] || {};
return option.getCurrentValue(selectedRange) || '';
}
getSelectedRange(ranges, staticRange) {
const focusedRangeIndex = ranges.findIndex(range => {
if (!range.startDate || !range.endDate || range.disabled) return false;
return staticRange.isSelected(range);
});
const selectedRange = ranges[focusedRangeIndex];
return { selectedRange, focusedRangeIndex };
}
render() {
const {
headerContent,
footerContent,
onPreviewChange,
inputRanges,
staticRanges,
ranges,
renderStaticRangeLabel,
rangeColors,
className,
} = this.props;
return (
<div className={cx(styles.definedRangesWrapper, className)}>
{headerContent}
<div className={styles.staticRanges}>
{staticRanges.map((staticRange, i) => {
const { selectedRange, focusedRangeIndex } = this.getSelectedRange(ranges, staticRange);
let labelContent;
if (staticRange.hasCustomRendering) {
labelContent = renderStaticRangeLabel(staticRange);
} else {
labelContent = staticRange.label;
}
return (
<button
type="button"
className={cx(styles.staticRange, {
[styles.staticRangeSelected]: Boolean(selectedRange),
})}
style={{
color: selectedRange
? selectedRange.color || rangeColors[focusedRangeIndex]
: null,
}}
key={i}
onClick={() => this.handleRangeChange(staticRange.range(this.props))}
onFocus={() => onPreviewChange && onPreviewChange(staticRange.range(this.props))}
onMouseOver={() =>
onPreviewChange && onPreviewChange(staticRange.range(this.props))
}
onMouseLeave={() => {
onPreviewChange && onPreviewChange();
}}>
<span tabIndex={-1} className={styles.staticRangeLabel}>
{labelContent}
</span>
</button>
);
})}
</div>
<div className={styles.inputRanges}>
{inputRanges.map((rangeOption, i) => (
<InputRangeField
key={i}
styles={styles}
label={rangeOption.label}
onFocus={() => this.setState({ focusedInput: i, rangeOffset: 0 })}
onBlur={() => this.setState({ rangeOffset: 0 })}
onChange={value => this.handleRangeChange(rangeOption.range(value, this.props))}
value={this.getRangeOptionValue(rangeOption)}
/>
))}
</div>
{footerContent}
</div>
);
}
}
DefinedRange.propTypes = {
inputRanges: PropTypes.array,
staticRanges: PropTypes.array,
ranges: PropTypes.arrayOf(rangeShape),
focusedRange: PropTypes.arrayOf(PropTypes.number),
onPreviewChange: PropTypes.func,
onChange: PropTypes.func,
footerContent: PropTypes.any,
headerContent: PropTypes.any,
rangeColors: PropTypes.arrayOf(PropTypes.string),
className: PropTypes.string,
renderStaticRangeLabel: PropTypes.func,
};
DefinedRange.defaultProps = {
inputRanges: defaultInputRanges,
staticRanges: defaultStaticRanges,
ranges: [],
rangeColors: ['#3d91ff', '#3ecf8e', '#fed14c'],
focusedRange: [0, 0],
};
export default DefinedRange;