lucid-ui
Version:
A UI component library from Xandr.
281 lines • 12.1 kB
JavaScript
/* eslint-disable react/prop-types */
import _, { omit } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import DayPicker from 'react-day-picker';
import { buildModernHybridComponent } from '../../util/state-management';
import { lucidClassNames } from '../../util/style-helpers';
import { getFirst } from '../../util/component-types';
import * as reducers from './DateSelect.reducers';
import InfiniteSlidePanel from '../InfiniteSlidePanel/InfiniteSlidePanel';
import CalendarMonth from '../CalendarMonth/CalendarMonth';
import ChevronIcon from '../Icon/ChevronIcon/ChevronIcon';
const cx = lucidClassNames.bind('&-DateSelect');
const DateUtils = DayPicker.DateUtils;
const NAV_BUTTON_SIZE = 32;
const clampMonthsShown = (monthsShown) => _.clamp(monthsShown, 1, 6);
const { any, bool, node, func, instanceOf, number, oneOf, string } = PropTypes;
const DateSelectCalendarMonth = (_props) => null;
DateSelectCalendarMonth.displayName = 'DateSelect.CalendarMonth';
DateSelectCalendarMonth.peek = {
description: `Child component to pass thru props to underlying CalendarMonth.`,
};
DateSelectCalendarMonth.propName = 'CalendarMonth';
const nonPassThroughs = [
'className',
'monthsShown',
'calendarsRendered',
'offset',
'from',
'to',
'selectMode',
'initialMonth',
'selectedDays',
'disabledDays',
'showDivider',
'onSwipe',
'onPrev',
'onNext',
'onSelectDate',
'isFontSizeRelative',
'showCursorHighlight',
'useSlidePanel',
'CalendarMonth',
'callbackId',
'initialState',
];
class DateSelect extends React.Component {
constructor(props) {
super(props);
this.rootRef = null;
this.handleDayClick = (day, { disabled }, event) => {
const { onSelectDate } = this.props;
if (!disabled) {
onSelectDate(day, { event, props: this.props });
}
};
this.handleDayMouseEnter = (day, { disabled }) => {
if (disabled) {
this.setState({
cursor: null,
});
}
else {
this.setState({
cursor: day,
});
}
};
this.handleDayMouseLeave = () => {
this.setState({
cursor: null,
});
};
this.handlePrev = ({ event }) => {
this.props.onPrev({ event, props: this.props });
};
this.handleNext = ({ event }) => {
this.props.onNext({ event, props: this.props });
};
this.componentDidMount = () => {
const { isFontSizeRelative, monthsShown: monthsShownRaw } = this.props;
const monthsShown = clampMonthsShown(monthsShownRaw);
if (isFontSizeRelative && this.rootRef) {
const rootElement = this.rootRef;
const { width, height } = rootElement.getBoundingClientRect();
const navButtonsWidth = NAV_BUTTON_SIZE * 2;
const oneMonthShownWidth = (width - navButtonsWidth) / monthsShown + navButtonsWidth;
const size = Math.sqrt(oneMonthShownWidth * height);
const relativeFontSize = Math.round(size / 24);
const relativeMinWidth = ((((width - navButtonsWidth) / monthsShown) * 10.1075) /
relativeFontSize) *
monthsShown +
navButtonsWidth;
rootElement.style.fontSize = `${relativeFontSize}px`;
rootElement.style.minWidth = `${relativeMinWidth}px`;
}
};
this.renderCalendarMonth = (monthOffset, isRangeSameDay, selectedDays, { key, initialMonth, cursor, from, to, disabledDays, selectMode, onDayClick, showCursorHighlight, onDayMouseEnter, onDayMouseLeave, ...rest }) => {
return (React.createElement(CalendarMonth, { key: key, className: cx('&-CalendarMonth'), monthOffset: monthOffset, initialMonth: initialMonth, cursor: cursor, from: isRangeSameDay ? null : from, to: isRangeSameDay ? null : to, selectedDays: isRangeSameDay ? from : selectedDays, disabledDays: disabledDays, selectMode: selectMode, onDayClick: onDayClick, onDayMouseEnter: showCursorHighlight ? onDayMouseEnter : _.noop, onDayMouseLeave: showCursorHighlight ? onDayMouseLeave : _.noop, ...rest }));
};
this.initialMonth = new Date(this.props.initialMonth);
this.state = {
offset: 0,
cursor: null,
};
}
render() {
const { className, monthsShown: monthsShownRaw, calendarsRendered, offset, from, to, selectMode, selectedDays, disabledDays, showDivider, onSwipe, showCursorHighlight, useSlidePanel, ...passThroughs } = this.props;
const { cursor } = this.state;
//@ts-ignore
// For some reason react-day-pickers type doesn't allow `null` values but
// we seem to be passing them in and it seems to "work" at least in that
// the code doesn't blow up.
const isRangeSameDay = DateUtils.isSameDay(from, to);
const calendarMonthProps = _.get(getFirst(this.props, DateSelect.CalendarMonth), 'props');
const monthsShown = clampMonthsShown(monthsShownRaw);
/* istanbul ignore next */
return (React.createElement("section", { ref: (ref) => (this.rootRef = ref), className: cx('&', className, {
'&-show-divider': showDivider,
}), style: {
minWidth: NAV_BUTTON_SIZE * 2 + 185 * monthsShown,
...passThroughs.style,
}, ...omit(passThroughs, nonPassThroughs) },
React.createElement("div", null,
React.createElement(ChevronIcon, { className: cx('&-chevron'), size: NAV_BUTTON_SIZE, isClickable: true, direction: 'left', onClick: this.handlePrev })),
useSlidePanel ? (React.createElement(InfiniteSlidePanel, { className: cx('&-InfiniteSlidePanel', '&-slidePanel'), totalSlides: calendarsRendered, slidesToShow: monthsShown, offset: offset, onSwipe: onSwipe },
React.createElement(InfiniteSlidePanel.Slide, { className: cx('&-slide') }, (slideOffset) => (React.createElement("div", { className: cx('&-slide-content') }, this.renderCalendarMonth(offset + slideOffset - offset, isRangeSameDay, selectedDays, {
key: slideOffset,
initialMonth: this.initialMonth,
cursor,
from,
to,
disabledDays,
selectMode,
onDayClick: this.handleDayClick,
showCursorHighlight,
onDayMouseEnter: this.handleDayMouseEnter,
onDayMouseLeave: this.handleDayMouseLeave,
...calendarMonthProps,
})))))) : (React.createElement("div", { className: cx('&-slidePanel', '&-slidePanel-simple') }, _.times(monthsShown, (calendarIndex) => (React.createElement("div", { className: cx('&-slide', '&-slide-simple'), key: calendarIndex },
React.createElement("div", { className: cx('&-slide-content') }, this.renderCalendarMonth(offset + calendarIndex, isRangeSameDay, selectedDays, {
initialMonth: this.initialMonth,
cursor,
from,
to,
disabledDays,
selectMode,
onDayClick: this.handleDayClick,
showCursorHighlight,
onDayMouseEnter: this.handleDayMouseEnter,
onDayMouseLeave: this.handleDayMouseLeave,
...calendarMonthProps,
}))))))),
React.createElement("div", null,
React.createElement(ChevronIcon, { className: cx('&-chevron'), size: NAV_BUTTON_SIZE, isClickable: true, direction: 'right', onClick: this.handleNext }))));
}
}
DateSelect.displayName = 'DateSelect';
DateSelect.CalendarMonth = DateSelectCalendarMonth;
DateSelect.peek = {
description: `A date selection component capabaple of supporting single date and date range selections.`,
categories: ['controls', 'selectors'],
madeFrom: ['InfiniteSlidePanel', 'CalendarMonth'],
};
DateSelect.propTypes = {
/**
Appended to the component-specific class names set on the root element.
*/
className: string,
/**
Number of calendar months to show. Min 1, suggested max 3. Actual max is 6.
*/
monthsShown: number,
calendarsRendered: number /**
Number of calendar months rendered at any given time (including those out
of view). In practice it should be at least (2 * monthsShown) + 2. It's
got some issues that still need to be ironed out but it works.
*/,
/**
The offset of the leftmost month in view, where 0 is the
\`initialMonth\`. Negative values will show previous months.
*/
offset: number,
/**
Sets the start date in a date range.
*/
from: instanceOf(Date),
/**
Sets the end date in a date range.
*/
to: instanceOf(Date),
/**
The next selection that is expected. Primarily used to preview expected
ranges when the cursor is on a target date.
*/
selectMode: oneOf(['day', 'from', 'to']),
/**
Sets first month in view on render. The 0 value for the \`offset\` prop
refers to this month.
*/
initialMonth: instanceOf(Date),
/**
Sets selected days. Passed through to \`CalendarMonth\` ->
\`react-day-picker\`. Can be a \`Date\`, array of \`Date\`s or a function
with the signature \`(date) => Boolean\`.
*/
selectedDays: any,
/**
Sets disabled days. Passed through to \`CalendarMonth\` ->
\`react-day-picker\`. Can be a \`Date\`, array of \`Date\`s or a function
with the signature \`(date) => Boolean\`.
*/
disabledDays: any,
/**
Display a divider between each month.
*/
showDivider: bool,
/**
Called when user's swipe would change the month \`offset\`. Callback
passes number of months swiped by the user (positive for forward swipes,
negative for backwards swipes). Signature:
\`(monthsSwiped, { event, props }) => {}\`
*/
onSwipe: func,
/**
Called when user clicks the previous button. Signature:
\`({ event, props }) => {}\`
*/
onPrev: func,
/**
Called when user clicks the next button. Signature:
\`({ event, props }) => {}\`
*/
onNext: func,
/**
Called when user selects a date. Callback passes a Date object as the
first argument. Signature: \`(selectedDate, { event, props }) => {}\`
*/
onSelectDate: func,
/**
Render initial font size relative to size of the component so it scales
with the calendar size.
*/
isFontSizeRelative: bool,
/**
Highlight dates and ranges based on cursor position.
*/
showCursorHighlight: bool,
/**
Render the calendar months in a touch-friendly slider with some being
rendered out-of-view. Set to \`false\` to disable this feature and gain a
performance boost.
*/
useSlidePanel: bool,
/**
Child component to pass thru props to underlying CalendarMonth.
*/
CalendarMonth: node,
};
DateSelect.defaultProps = {
monthsShown: 1,
calendarsRendered: 6,
offset: 0,
from: null,
to: null,
initialMonth: new Date(),
selectedDays: () => false,
disabledDays: () => false,
showDivider: false,
onSwipe: _.noop,
onPrev: _.noop,
onNext: _.noop,
onSelectDate: _.noop,
isFontSizeRelative: false,
showCursorHighlight: true,
useSlidePanel: true,
};
DateSelect.reducers = reducers;
export default buildModernHybridComponent(DateSelect, { reducers });
export { DateSelect as DateSelectDumb };
//# sourceMappingURL=DateSelect.js.map