UNPKG

date-range-picker-mui

Version:
400 lines (383 loc) 20.5 kB
'use strict'; var React = require('react'); var material = require('@mui/material'); var dateFns = require('date-fns'); var ArrowRightAltIcon = require('@mui/icons-material/ArrowRightAlt'); var ChevronLeftIcon = require('@mui/icons-material/ChevronLeft'); var ChevronRightIcon = require('@mui/icons-material/ChevronRight'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React); const chunks = (array, size) => (Array.from({ length: Math.ceil(array.length / size) }, (_v, i) => array.slice(i * size, i * size + size))); // Date const getDaysInMonth = (date, locale) => { const startWeek = dateFns.startOfWeek(dateFns.startOfMonth(date), { locale }); const endWeek = dateFns.endOfWeek(dateFns.endOfMonth(date), { locale }); const days = []; for (let curr = startWeek; dateFns.isBefore(curr, endWeek);) { days.push(curr); curr = dateFns.addDays(curr, 1); } return days; }; const isStartOfRange = ({ startDate }, day) => (startDate && dateFns.isSameDay(day, startDate)); const isEndOfRange = ({ endDate }, day) => (endDate && dateFns.isSameDay(day, endDate)); const inDateRange = ({ startDate, endDate }, day) => (startDate && endDate && (dateFns.isWithinInterval(day, { start: startDate, end: endDate }) || dateFns.isSameDay(day, startDate) || dateFns.isSameDay(day, endDate))); const isRangeSameDay = ({ startDate, endDate }) => { if (startDate && endDate) { return dateFns.isSameDay(startDate, endDate); } return false; }; const parseOptionalDate = (date, defaultValue) => { if (date) { const parsed = date instanceof Date ? date : dateFns.parseISO(date); if (dateFns.isValid(parsed)) return parsed; } return defaultValue; }; const getValidatedMonths = (range, minDate, maxDate) => { const { startDate, endDate } = range; if (startDate && endDate) { const newStart = dateFns.max([startDate, minDate]); const newEnd = dateFns.min([endDate, maxDate]); return [newStart, dateFns.isSameMonth(newStart, newEnd) ? dateFns.addMonths(newStart, 1) : newEnd]; } return [startDate, endDate]; }; const getDefaultRanges = (date, locale) => [ { label: 'Today', startDate: date, endDate: date, }, { label: 'Yesterday', startDate: dateFns.addDays(date, -1), endDate: dateFns.addDays(date, -1), }, { label: 'This Week', startDate: dateFns.startOfWeek(date, { locale }), endDate: dateFns.endOfWeek(date, { locale }), }, { label: 'Last Week', startDate: dateFns.startOfWeek(dateFns.addWeeks(date, -1), { locale }), endDate: dateFns.endOfWeek(dateFns.addWeeks(date, -1), { locale }), }, { label: 'Last 7 Days', startDate: dateFns.addWeeks(date, -1), endDate: date, }, { label: 'This Month', startDate: dateFns.startOfMonth(date), endDate: dateFns.endOfMonth(date), }, { label: 'Last Month', startDate: dateFns.startOfMonth(dateFns.addMonths(date, -1)), endDate: dateFns.endOfMonth(dateFns.addMonths(date, -1)), }, { label: 'This Year', startDate: dateFns.startOfYear(date), endDate: dateFns.endOfYear(date), }, { label: 'Last Year', startDate: dateFns.startOfYear(dateFns.addYears(date, -1)), endDate: dateFns.endOfYear(dateFns.addYears(date, -1)), }, ]; const generateYears = (relativeTo, count) => { const half = Math.floor(count / 2); return Array(count) .fill(0) .map((_y, i) => relativeTo.getFullYear() - half + i); // TODO: make part of the state }; const Header = ({ date, setDate, nextDisabled, prevDisabled, onClickNext, onClickPrevious, locale }) => { const MONTHS = typeof locale !== 'undefined' ? [...Array(12).keys()].map(d => d).map(d => { var _a; return (_a = locale.localize) === null || _a === void 0 ? void 0 : _a.month(d, { width: 'abbreviated', context: 'standalone' }); }) : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']; const handleMonthChange = (event) => { setDate(dateFns.setMonth(date, parseInt(event.target.value, 10))); }; const handleYearChange = (event) => { setDate(dateFns.setYear(date, parseInt(event.target.value, 10))); }; return (React.createElement(material.Grid, { container: true, justifyContent: "space-between", alignItems: "center" }, React.createElement(material.Grid, { item: true, sx: { padding: '5px' } }, React.createElement(material.IconButton, { sx: { padding: '10px', '&:hover': { background: 'none', }, }, disabled: prevDisabled, onClick: onClickPrevious }, React.createElement(ChevronLeftIcon, { color: prevDisabled ? 'disabled' : 'action' }))), React.createElement(material.Grid, { item: true }, React.createElement(material.FormControl, { variant: "standard" }, React.createElement(material.Select, { value: dateFns.getMonth(date), onChange: handleMonthChange, MenuProps: { disablePortal: true } }, MONTHS.map((month, idx) => (React.createElement(material.MenuItem, { key: month, value: idx }, month)))))), React.createElement(material.Grid, { item: true }, React.createElement(material.FormControl, { variant: "standard" }, React.createElement(material.Select, { value: dateFns.getYear(date), onChange: handleYearChange, MenuProps: { disablePortal: true } }, generateYears(date, 30).map((year) => (React.createElement(material.MenuItem, { key: year, value: year }, year)))))), React.createElement(material.Grid, { item: true, sx: { padding: '5px' } }, React.createElement(material.IconButton, { sx: { padding: '10px', '&:hover': { background: 'none', }, }, disabled: nextDisabled, onClick: onClickNext }, React.createElement(ChevronRightIcon, { color: nextDisabled ? 'disabled' : 'action' }))))); }; var NavigationAction; (function (NavigationAction) { // eslint-disable-next-line no-unused-vars NavigationAction[NavigationAction["Previous"] = -1] = "Previous"; // eslint-disable-next-line no-unused-vars NavigationAction[NavigationAction["Next"] = 1] = "Next"; })(NavigationAction || (NavigationAction = {})); const DayComponent = ({ startOfRange, endOfRange, disabled, highlighted, outlined, filled, onClick, onHover, value, }) => { return (React.createElement(material.Box, { sx: { display: 'flex', // eslint-disable-next-line no-nested-ternary borderRadius: startOfRange ? '50% 0 0 50%' : endOfRange ? '0 50% 50% 0' : undefined, backgroundColor: (theme) => !disabled && highlighted ? theme.palette.primary.light : undefined, } }, React.createElement(material.IconButton, { sx: Object.assign({ height: '36px', width: '36px', padding: 0, border: (theme) => !disabled && outlined ? `1px solid ${theme.palette.primary.dark}` : undefined }, (!disabled && filled ? { '&:hover': { backgroundColor: (theme) => theme.palette.primary.dark, }, backgroundColor: (theme) => theme.palette.primary.dark, } : {})), disabled: disabled, onClick: onClick, onMouseOver: onHover }, React.createElement(material.Typography, { sx: { lineHeight: 1.6, color: (theme) => !disabled ? (filled ? theme.palette.primary.contrastText : theme.palette.text.primary) : theme.palette.text.secondary, }, variant: "body2" }, value)))); }; const Month = (props) => { var _a; const { helpers, handlers, value: date, dateRange, marker, setValue: setDate, minDate, maxDate, locale } = props; const weekStartsOn = ((_a = locale === null || locale === void 0 ? void 0 : locale.options) === null || _a === void 0 ? void 0 : _a.weekStartsOn) || 0; const localizer = locale === null || locale === void 0 ? void 0 : locale.localize; const WEEK_DAYS = localizer ? [...Array(7).keys()].map(d => ((d + weekStartsOn) % 7)).map(d => localizer.day(d, { width: 'short', context: 'standalone' })) : ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; const [back, forward] = props.navState; return (React.createElement(material.Paper, { square: true, elevation: 0, sx: { width: 290 } }, React.createElement(material.Grid, { container: true }, React.createElement(Header, { date: date, setDate: setDate, nextDisabled: !forward, prevDisabled: !back, onClickPrevious: () => handlers.onMonthNavigate(marker, NavigationAction.Previous), onClickNext: () => handlers.onMonthNavigate(marker, NavigationAction.Next), locale: locale }), React.createElement(material.Grid, { item: true, container: true, direction: "row", justifyContent: "space-between", sx: { marginTop: "10px", paddingLeft: "30px", paddingRight: "30px" } }, WEEK_DAYS.map((day, index) => (React.createElement(material.Typography, { color: "textSecondary", key: index, variant: "caption" }, day)))), React.createElement(material.Grid, { item: true, container: true, direction: "column", justifyContent: "space-between", sx: { paddingLeft: '15px', paddingRight: '15px', marginTop: '15px', marginBottom: '20px' } }, chunks(getDaysInMonth(date, locale), 7).map((week, idx) => (React.createElement(material.Grid, { key: idx, container: true, direction: "row", justifyContent: "center" }, week.map((day) => { const isStart = isStartOfRange(dateRange, day); const isEnd = isEndOfRange(dateRange, day); const isRangeOneDay = isRangeSameDay(dateRange); const highlighted = inDateRange(dateRange, day) || helpers.inHoverRange(day); return (React.createElement(DayComponent, { key: dateFns.format(day, "dd-MM-yyyy"), filled: isStart || isEnd, outlined: dateFns.isToday(day), highlighted: highlighted && !isRangeOneDay, disabled: !dateFns.isSameMonth(date, day) || !dateFns.isWithinInterval(day, { start: minDate, end: maxDate }), startOfRange: isStart && !isRangeOneDay, endOfRange: isEnd && !isRangeOneDay, onClick: () => handlers.onDayClick(day), onHover: () => handlers.onDayHover(day), value: dateFns.getDate(day) })); })))))))); }; const isSameRange = (first, second) => { const { startDate: fStart, endDate: fEnd } = first; const { startDate: sStart, endDate: sEnd } = second; if (fStart && sStart && fEnd && sEnd) { return dateFns.isSameDay(fStart, sStart) && dateFns.isSameDay(fEnd, sEnd); } return false; }; const DefinedRanges = ({ ranges, setRange, selectedRange, }) => (React.createElement(material.List, null, ranges.map((range, idx) => (React.createElement(material.ListItemButton, { key: idx, onClick: () => setRange(range), sx: [ isSameRange(range, selectedRange) && { backgroundColor: (theme) => theme.palette.primary.dark, color: 'primary.contrastText', '&:hover': { color: 'inherit' } } ] }, React.createElement(material.ListItemText, { primaryTypographyProps: { variant: 'body2', sx: { fontWeight: isSameRange(range, selectedRange) ? 'bold' : 'normal', }, } }, range.label)))))); const MARKERS = { FIRST_MONTH: Symbol('firstMonth'), SECOND_MONTH: Symbol('secondMonth'), }; /* eslint-disable object-curly-newline */ const Menu = (props) => { const { ranges, dateRange, minDate, maxDate, firstMonth, setFirstMonth, secondMonth, setSecondMonth, setDateRange, helpers, handlers, locale } = props; const { startDate, endDate } = dateRange; const canNavigateCloser = dateFns.differenceInCalendarMonths(secondMonth, firstMonth) >= 2; const commonProps = { dateRange, minDate, maxDate, helpers, handlers, }; return (React.createElement(material.Paper, { elevation: 5, square: true }, React.createElement(material.Grid, { container: true, direction: "row", wrap: "nowrap" }, React.createElement(material.Grid, null, React.createElement(DefinedRanges, { selectedRange: dateRange, ranges: ranges, setRange: setDateRange })), React.createElement(material.Divider, { orientation: "vertical", flexItem: true }), React.createElement(material.Grid, null, React.createElement(material.Grid, { container: true, sx: { padding: '20px 70px' }, alignItems: "center" }, React.createElement(material.Grid, { item: true, sx: { flex: 1, textAlign: 'center' } }, React.createElement(material.Typography, { variant: "subtitle1" }, startDate ? dateFns.format(startDate, 'dd MMMM yyyy', { locale }) : 'Start Date')), React.createElement(material.Grid, { item: true, sx: { flex: 1, textAlign: 'center' } }, React.createElement(ArrowRightAltIcon, { color: "action" })), React.createElement(material.Grid, { item: true, sx: { flex: 1, textAlign: 'center' } }, React.createElement(material.Typography, { variant: "subtitle1" }, endDate ? dateFns.format(endDate, 'dd MMMM yyyy', { locale }) : 'End Date'))), React.createElement(material.Divider, null), React.createElement(material.Grid, { container: true, direction: "row", justifyContent: "center", wrap: "nowrap" }, React.createElement(Month, Object.assign({}, commonProps, { value: firstMonth, setValue: setFirstMonth, navState: [true, canNavigateCloser], marker: MARKERS.FIRST_MONTH, locale: locale })), React.createElement(material.Divider, { orientation: "vertical", flexItem: true }), React.createElement(Month, Object.assign({}, commonProps, { value: secondMonth, setValue: setSecondMonth, navState: [canNavigateCloser, true], marker: MARKERS.SECOND_MONTH, locale: locale }))))))); }; const DateRangePicker = (props) => { const today = new Date(); const { open, onChange, initialDateRange, minDate, maxDate, definedRanges = getDefaultRanges(new Date(), props.locale), locale, } = props; const minDateValid = parseOptionalDate(minDate, dateFns.addYears(today, -10)); const maxDateValid = parseOptionalDate(maxDate, dateFns.addYears(today, 10)); const [intialFirstMonth, initialSecondMonth] = getValidatedMonths(initialDateRange || {}, minDateValid, maxDateValid); const [dateRange, setDateRange] = React__namespace.useState(Object.assign({}, initialDateRange)); const [hoverDay, setHoverDay] = React__namespace.useState(); const [firstMonth, setFirstMonth] = React__namespace.useState(intialFirstMonth || today); const [secondMonth, setSecondMonth] = React__namespace.useState(initialSecondMonth || dateFns.addMonths(firstMonth, 1)); const { startDate, endDate } = dateRange; // handlers const setFirstMonthValidated = (date) => { if (dateFns.isBefore(date, secondMonth)) { setFirstMonth(date); } }; const setSecondMonthValidated = (date) => { if (dateFns.isAfter(date, firstMonth)) { setSecondMonth(date); } }; const setDateRangeValidated = (range) => { let { startDate: newStart, endDate: newEnd } = range; if (newStart && newEnd) { range.startDate = newStart = dateFns.max([newStart, minDateValid]); range.endDate = newEnd = dateFns.min([newEnd, maxDateValid]); setDateRange(range); onChange(range); setFirstMonth(newStart); setSecondMonth(dateFns.isSameMonth(newStart, newEnd) ? dateFns.addMonths(newStart, 1) : newEnd); } else { const emptyRange = {}; setDateRange(emptyRange); onChange(emptyRange); setFirstMonth(today); setSecondMonth(dateFns.addMonths(firstMonth, 1)); } }; const onDayClick = (day) => { if (startDate && !endDate && !dateFns.isBefore(day, startDate)) { const newRange = { startDate, endDate: day }; onChange(newRange); setDateRange(newRange); } else { setDateRange({ startDate: day, endDate: undefined }); } setHoverDay(day); }; const onMonthNavigate = (marker, action) => { if (marker === MARKERS.FIRST_MONTH) { const firstNew = dateFns.addMonths(firstMonth, action); if (dateFns.isBefore(firstNew, secondMonth)) setFirstMonth(firstNew); } else { const secondNew = dateFns.addMonths(secondMonth, action); if (dateFns.isBefore(firstMonth, secondNew)) setSecondMonth(secondNew); } }; const onDayHover = (date) => { if (startDate && !endDate) { if (!hoverDay || !dateFns.isSameDay(date, hoverDay)) { setHoverDay(date); } } }; // helpers const inHoverRange = (day) => (startDate && !endDate && hoverDay && dateFns.isAfter(hoverDay, startDate) && dateFns.isWithinInterval(day, { start: startDate, end: hoverDay })); const helpers = { inHoverRange, }; const handlers = { onDayClick, onDayHover, onMonthNavigate, }; return open ? (React__namespace.createElement(Menu, { dateRange: dateRange, minDate: minDateValid, maxDate: maxDateValid, ranges: definedRanges, firstMonth: firstMonth, secondMonth: secondMonth, setFirstMonth: setFirstMonthValidated, setSecondMonth: setSecondMonthValidated, setDateRange: setDateRangeValidated, helpers: helpers, handlers: handlers, locale: locale })) : null; }; const DateRangePickerWrapper = (props) => { const { closeOnClickOutside, wrapperClassName, toggle, open, } = props; const handleToggle = () => { if (closeOnClickOutside === false) { return; } toggle(); }; const handleKeyPress = (event) => (event === null || event === void 0 ? void 0 : event.key) === 'Escape' && handleToggle(); return (React.createElement(material.Box, { sx: { position: 'relative' } }, open && (React.createElement(material.Box, { sx: { position: 'fixed', height: '100vh', width: '100vw', bottom: 0, zIndex: 0, right: 0, left: 0, top: 0, }, onKeyPress: handleKeyPress, onClick: handleToggle })), React.createElement(material.Box, { sx: { position: 'relative', zIndex: 1 }, className: wrapperClassName }, React.createElement(DateRangePicker, Object.assign({}, props))))); }; const DateRangePickerExporter = (props) => (React.createElement(DateRangePickerWrapper, Object.assign({}, props))); exports.DateRangePicker = DateRangePickerExporter; exports.DateRangePickerComponent = DateRangePicker; //# sourceMappingURL=index.js.map