Last active
August 18, 2023 04:25
-
-
Save trinhvanminh/6b0e68c2abc964fd823ec36e9556186b to your computer and use it in GitHub Desktop.
custom mui date range picker (mobile and desktop)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
============================================components/PickersActionBar.js============================================ | |
import { Button, Stack } from '@mui/material'; | |
import { useLocaleText } from '@mui/x-date-pickers/internals'; | |
export default function PickersActionBar({ onCancel, onAccept, disabled }) { | |
const { cancelButtonLabel, okButtonLabel } = useLocaleText(); | |
return ( | |
<Stack | |
flexDirection="row" | |
justifyContent="flex-end" | |
zIndex={1} | |
p={2} | |
borderTop={1} | |
borderColor="divider" | |
bgcolor="#fff" | |
> | |
<Button onClick={onCancel}>{cancelButtonLabel}</Button> | |
<Button onClick={onAccept} disabled={disabled} sx={{ ml: 2 }}> | |
{okButtonLabel} | |
</Button> | |
</Stack> | |
); | |
} | |
============================================components/PickersShortcuts.js============================================ | |
import { Box, List, ListItemButton, ListItemText } from '@mui/material'; | |
import { useRef } from 'react'; | |
function ShortcutItem({ label, getValue, isValid, onChange, onDoubleClick, disableClear }) { | |
const timeoutRef = useRef(); | |
const [from, to] = getValue(); | |
const isValidDate = from || to ? isValid(from) || isValid(to) : !disableClear; | |
const handleOnClick = () => { | |
timeoutRef.current && clearTimeout(timeoutRef.current); | |
timeoutRef.current = setTimeout(() => { | |
onChange([from, to]); | |
}, 50); | |
}; | |
const handleOnDoubleClick = () => { | |
timeoutRef.current && clearTimeout(timeoutRef.current); | |
onDoubleClick([from, to]); | |
}; | |
return ( | |
<ListItemButton key={label} disabled={!isValidDate} onClick={handleOnClick} onDoubleClick={handleOnDoubleClick}> | |
<ListItemText primary={label} /> | |
</ListItemButton> | |
); | |
} | |
export default function PickersShortcuts({ items, className, isValid, onChange, onDoubleClick, disableClear }) { | |
return ( | |
<Box | |
className={className} | |
sx={{ | |
borderRight: 1, | |
borderColor: 'divider', | |
}} | |
> | |
<List> | |
{items.map((item, iKey) => ( | |
<ShortcutItem | |
key={iKey} | |
{...item} | |
isValid={isValid} | |
onChange={onChange} | |
onDoubleClick={onDoubleClick} | |
disableClear={disableClear} | |
/> | |
))} | |
</List> | |
</Box> | |
); | |
} | |
============================================components/PickersToolbar.js============================================ | |
import { Box } from '@mui/material'; | |
import { | |
PickersToolbar as MuiPickersToolbar, | |
PickersToolbarButton, | |
useLocaleText, | |
} from '@mui/x-date-pickers/internals'; | |
import { EN_DASH } from '../enhance'; | |
const PickersToolbar = (props) => { | |
const localeText = useLocaleText(); | |
const { start, end, dateRangePickerToolbarTitle } = localeText; | |
// eslint-disable-next-line no-unused-vars | |
const { children, value, toolbarPlaceholder, toolbarTitle, ...restProps } = props; | |
const [from, to] = value || {}; | |
const fromDateValue = from ? from.format('DD MMM') : start; | |
const toDateValue = to ? to.format('DD MMM') : end; | |
const fromDateSelected = !from || (from && to); | |
return ( | |
<MuiPickersToolbar {...restProps} toolbarTitle={toolbarTitle || dateRangePickerToolbarTitle}> | |
<Box> | |
<PickersToolbarButton value={fromDateValue} variant="h5" disabled selected={fromDateSelected} /> | |
{` ${toolbarPlaceholder || EN_DASH} `} | |
<PickersToolbarButton value={toDateValue} variant="h5" disabled selected={!fromDateSelected} /> | |
</Box> | |
</MuiPickersToolbar> | |
); | |
}; | |
export default PickersToolbar; | |
============================================components/PickersDay.js============================================ | |
import { Box } from '@mui/material'; | |
import { PickersDay as MuiPickersDay } from '@mui/x-date-pickers'; | |
import { useRef } from 'react'; | |
import { ThemeExtensions } from 'styles/theme'; | |
// disable onKeydown events | |
// eslint-disable-next-line no-unused-vars | |
export default function PickersDay({ value, onChange, onDoubleClick, onKeyDown, ...restProps }) { | |
const timeoutRef = useRef(); | |
const { day, outsideCurrentMonth } = restProps; | |
const [fromDate, toDate] = value; | |
if (outsideCurrentMonth) { | |
return <MuiPickersDay {...restProps} />; | |
} | |
const isDateInScope = fromDate && toDate && day.isBetween(fromDate, toDate, 'day', '[]'); | |
const isSelectedFromDate = fromDate && day.isSame(fromDate, 'day'); | |
const isSelectedToDate = toDate && day.isSame(toDate, 'day'); | |
const isSelected = isSelectedFromDate || isSelectedToDate; | |
const handleOnDaySelect = (newDay) => { | |
restProps.onDaySelect(newDay); | |
timeoutRef.current && clearTimeout(timeoutRef.current); | |
timeoutRef.current = setTimeout(() => { | |
onChange(newDay); | |
}, 50); | |
}; | |
const handleOnDoubleClick = (newDay) => { | |
timeoutRef.current && clearTimeout(timeoutRef.current); | |
onDoubleClick(newDay); | |
}; | |
return ( | |
<Box | |
sx={{ | |
backgroundColor: () => toDate && isDateInScope && ThemeExtensions.getHoverColor('primary'), | |
...(isSelectedFromDate && { | |
borderTopLeftRadius: '50%', | |
borderBottomLeftRadius: '50%', | |
}), | |
...(isSelectedToDate && { | |
borderTopRightRadius: '50%', | |
borderBottomRightRadius: '50%', | |
}), | |
'&:first-of-type': { | |
borderTopLeftRadius: '50%', | |
borderBottomLeftRadius: '50%', | |
}, | |
'&:last-of-type': { | |
borderTopRightRadius: '50%', | |
borderBottomRightRadius: '50%', | |
}, | |
}} | |
onDoubleClick={() => handleOnDoubleClick(day)} | |
> | |
<MuiPickersDay {...restProps} onDaySelect={handleOnDaySelect} selected={isSelected} /> | |
</Box> | |
); | |
} | |
============================================enhance/index.js============================================ | |
import moment from 'moment'; | |
import LabelResources from 'resources/LabelResources'; | |
export const sxProps = { | |
toDate: { | |
'.MuiPickersCalendarHeader-root': { | |
cursor: 'default', | |
'.MuiPickersCalendarHeader-labelContainer': { | |
flex: 1, | |
justifyContent: 'center', | |
cursor: 'default', | |
'.MuiPickersFadeTransitionGroup-root': { | |
mr: -5.5, | |
cursor: 'text', | |
}, | |
}, | |
'.MuiPickersArrowSwitcher-root': { | |
userSelect: 'none', | |
}, | |
}, | |
}, | |
fromDate: { | |
'.MuiPickersCalendarHeader-root': { | |
cursor: 'default', | |
flexDirection: 'row-reverse', | |
pl: 1.5, | |
pr: 3, | |
'.MuiPickersCalendarHeader-labelContainer': { | |
flex: 1, | |
cursor: 'default', | |
justifyContent: 'center', | |
'.MuiPickersFadeTransitionGroup-root': { | |
ml: -4.25, | |
cursor: 'text', | |
}, | |
}, | |
'.MuiPickersArrowSwitcher-root': { | |
userSelect: 'none', | |
}, | |
}, | |
}, | |
popper: { | |
zIndex: 'modal', | |
mt: (theme) => `${theme.spacing(0.5)} !important`, | |
mb: (theme) => `${theme.spacing(1)} !important`, | |
}, | |
dateRangePickerWrapper: { | |
boxShadow: 8, | |
borderRadius: 1, | |
overflow: 'hidden', | |
transformOrigin: 'center top', | |
}, | |
mobileDatePickerWrapper: { | |
position: 'absolute', | |
top: '50%', | |
left: '50%', | |
transform: 'translate(-50%, -50%)', | |
outline: 'none', | |
'.MuiPickersLayout-root': { | |
outline: 'none', | |
borderRadius: 1, | |
}, | |
}, | |
}; | |
export const shortcutItems = [ | |
{ | |
label: LabelResources.TODAY, | |
getValue: () => { | |
return [moment().startOf('day'), moment().endOf('day')]; | |
}, | |
}, | |
{ | |
label: LabelResources.YESTERDAY, | |
getValue: () => { | |
return [moment().subtract(1, 'day').startOf('day'), moment().subtract(1, 'day').endOf('day')]; | |
}, | |
}, | |
{ | |
label: LabelResources.THIS_WEEK, | |
getValue: () => { | |
return [moment().startOf('week'), moment().endOf('week')]; | |
}, | |
}, | |
{ | |
label: LabelResources.LAST_WEEK, | |
getValue: () => { | |
return [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')]; | |
}, | |
}, | |
{ | |
label: LabelResources.THIS_MONTH, | |
getValue: () => { | |
return [moment().startOf('month'), moment().endOf('month')]; | |
}, | |
}, | |
{ | |
label: LabelResources.LAST_MONTH, | |
getValue: () => { | |
return [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]; | |
}, | |
}, | |
{ | |
label: LabelResources.RESET, | |
getValue: () => { | |
return [null, null]; | |
}, | |
}, | |
]; | |
export const DASH = '-'; | |
export const EN_DASH = '\u2013'; // also dash but longer --> '–' | |
export const DEFAULT_TIMEMOUT = 329; | |
============================================MobileDateRangePicker.js============================================ | |
import { Box, Modal } from '@mui/material'; | |
import { StaticDatePicker } from '@mui/x-date-pickers'; | |
import moment from 'moment'; | |
import { memo } from 'react'; | |
import PickersDay from './components/PickersDay'; | |
import PickersToolbar from './components/PickersToolbar'; | |
import { sxProps } from './enhance'; | |
const MobileDateRangePicker = ({ | |
// commonProps.custom props | |
open, | |
viewMonth, | |
tempValue, | |
onAccept, | |
onCancel, | |
onChange, | |
onNextMonthClick, | |
onPreviousMonthClick, | |
// | |
onClose, | |
onDoubleClick, | |
...rest | |
}) => { | |
const dateRangeSlotProps = { | |
day: { | |
value: tempValue, | |
onChange, | |
onDoubleClick, | |
}, | |
actionBar: { | |
onAccept, | |
onCancel, | |
}, | |
toolbar: { | |
value: tempValue, | |
}, | |
previousIconButton: { | |
onClick: onPreviousMonthClick, | |
}, | |
nextIconButton: { | |
onClick: onNextMonthClick, | |
}, | |
}; | |
return ( | |
<Modal open={open} onClose={onClose}> | |
<Box sx={sxProps.mobileDatePickerWrapper}> | |
<StaticDatePicker | |
{...rest} | |
slotProps={dateRangeSlotProps} | |
slots={{ day: PickersDay, toolbar: PickersToolbar }} | |
value={viewMonth || moment()} | |
/> | |
</Box> | |
</Modal> | |
); | |
}; | |
export default memo(MobileDateRangePicker); | |
============================================DesktopDateRangePicker.js============================================ | |
import { Box, Grow, Popper, Stack } from '@mui/material'; | |
import { StaticDatePicker } from '@mui/x-date-pickers'; | |
import moment from 'moment'; | |
import { memo } from 'react'; | |
import PickersActionBar from './components/PickersActionBar'; | |
import PickersDay from './components/PickersDay'; | |
import PickersShortcuts from './components/PickersShortcuts'; | |
import { DEFAULT_TIMEMOUT, shortcutItems, sxProps } from './enhance'; | |
const DesktopDateRangePicker = ({ | |
// commonProps.custom props | |
open, | |
anchorEl, | |
viewMonth, | |
tempValue, | |
onAccept, | |
onCancel, | |
onChange, | |
onNextMonthClick, | |
onPreviousMonthClick, | |
// | |
onShortcutsChange, | |
onDoubleClick, | |
disableClear, | |
...rest | |
}) => { | |
const commonDaySlotProps = { | |
value: tempValue, | |
onChange, | |
onDoubleClick, | |
}; | |
const fromDateSlotProps = { | |
shortcuts: { | |
items: shortcutItems, | |
onChange: onShortcutsChange, | |
onDoubleClick, | |
disableClear, | |
}, | |
day: commonDaySlotProps, | |
previousIconButton: { onClick: onPreviousMonthClick }, | |
}; | |
const fromDateSlots = { | |
shortcuts: PickersShortcuts, | |
day: PickersDay, | |
nextIconButton: () => null, | |
}; | |
const toDateSlotProps = { | |
day: commonDaySlotProps, | |
nextIconButton: { onClick: onNextMonthClick }, | |
}; | |
const toDateSlots = { | |
day: PickersDay, | |
previousIconButton: () => null, | |
}; | |
return ( | |
<Popper placement="bottom-start" open={open} sx={sxProps.popper} anchorEl={anchorEl} transition> | |
{({ TransitionProps }) => ( | |
<Grow {...TransitionProps} timeout={DEFAULT_TIMEMOUT}> | |
<Box sx={sxProps.dateRangePickerWrapper}> | |
<Stack flexDirection="row"> | |
<StaticDatePicker | |
sx={sxProps.fromDate} | |
slots={fromDateSlots} | |
slotProps={fromDateSlotProps} | |
{...rest} | |
displayStaticWrapperAs="desktop" | |
value={viewMonth || moment()} | |
/> | |
<StaticDatePicker | |
sx={sxProps.toDate} | |
slots={toDateSlots} | |
slotProps={toDateSlotProps} | |
{...rest} | |
displayStaticWrapperAs="desktop" | |
value={viewMonth ? moment(viewMonth).add(1, 'month') : moment().add(1, 'month')} | |
defaultCalendarMonth={moment().add(1, 'months')} | |
/> | |
</Stack> | |
{/* Action Bar */} | |
<PickersActionBar onCancel={onCancel} onAccept={onAccept} disabled={!tempValue[0] || !tempValue[1]} /> | |
</Box> | |
</Grow> | |
)} | |
</Popper> | |
); | |
}; | |
export default memo(DesktopDateRangePicker); | |
============================================index.js============================================ | |
import { Close as CloseIcon, InsertInvitation as InsertInvitationIcon } from '@mui/icons-material'; | |
import { Box, ClickAwayListener, IconButton, InputAdornment, TextField } from '@mui/material'; | |
import { DEFAULT_DESKTOP_MODE_MEDIA_QUERY } from '@mui/x-date-pickers'; | |
import withMediaQuery from 'hocs/withMediaQuery'; | |
import moment from 'moment'; | |
import PropTypes from 'prop-types'; | |
import { PureComponent, createRef } from 'react'; | |
import Resources from 'resources'; | |
import DesktopDateRangePicker from './DesktopDateRangePicker'; | |
import MobileDateRangePicker from './MobileDateRangePicker'; | |
import { DASH } from './enhance'; | |
const initState = { | |
tempValue: [null, null], | |
viewMonth: null, | |
open: false, | |
focused: false, | |
}; | |
class DateRangePicker extends PureComponent { | |
state = initState; | |
_anchorRef = createRef(); | |
_inputRef = createRef(); | |
componentDidMount() { | |
const { value } = this.props; | |
this.setState({ tempValue: value }); | |
} | |
componentDidUpdate(prevProps, prevState) { | |
const { value } = this.props; | |
const { tempValue, open } = this.state; | |
if (prevState.tempValue[0] !== tempValue[0] || prevState.tempValue[1] !== tempValue[1]) { | |
if (tempValue[0] !== null) this.setState({ viewMonth: tempValue[0] }); | |
} | |
if (prevProps.value[0] !== value[0] || prevState.open !== open) { | |
this.setState({ tempValue: value, viewMonth: value[0] }); | |
} | |
if (prevState.open !== open) { | |
if (open) { | |
window.addEventListener('keydown', this._handleEscPressed); | |
} else { | |
window.removeEventListener('keydown', this._handleEscPressed); | |
} | |
} | |
} | |
render() { | |
const { | |
label, | |
fullWidth, | |
required, | |
disableFuture, | |
disablePast, | |
helperText, | |
value, | |
localeText, | |
error, | |
format, | |
disableClear, | |
matches: isDesktop, | |
} = this.props; | |
const { tempValue, viewMonth, focused, open } = this.state; | |
const [minDate, maxDate] = this._getSelectableRange(tempValue); | |
const isExistedValue = | |
(tempValue?.length && (tempValue[0] || tempValue[1])) || (value?.length && (value[0] || value[1])); | |
const isClearable = isExistedValue && !disableClear; | |
const InputProps = { | |
endAdornment: isDesktop ? ( | |
<InputAdornment position="end"> | |
{isClearable ? ( | |
<IconButton edge="end" onClick={this._handleClearIconClick}> | |
<CloseIcon /> | |
</IconButton> | |
) : ( | |
<IconButton edge="end" onClick={this._handleCalendarIconClick}> | |
<InsertInvitationIcon /> | |
</IconButton> | |
)} | |
</InputAdornment> | |
) : null, | |
}; | |
const commonProps = { | |
// mui date picker props (rest) | |
views: ['day'], | |
minDate, | |
maxDate, | |
disablePast, | |
disableFuture, | |
// custom props | |
open, | |
viewMonth, | |
tempValue, | |
onCancel: this._handleCancel, | |
onAccept: this._handleAccept, | |
onChange: this._handleDateChange, | |
onPreviousMonthClick: this._handlePreviousMonthClick, | |
onNextMonthClick: this._handleNextMonthClick, | |
}; | |
const toLocaleText = isDesktop ? localeText.to : DASH; | |
return ( | |
<ClickAwayListener onClickAway={this._handleClickAway}> | |
<Box ref={this._anchorRef}> | |
<TextField | |
readOnly | |
required={required} | |
fullWidth={fullWidth} | |
label={label} | |
inputRef={this._inputRef} | |
onClick={this._handleTextFieldClick} | |
helperText={helperText} | |
error={error} | |
InputProps={InputProps} | |
value={this._renderDisplayText(toLocaleText)} | |
placeholder={`${format} ${toLocaleText} ${format}`} | |
sx={{ caretColor: 'transparent' }} | |
focused={focused} | |
autoComplete="off" | |
/> | |
{isDesktop ? ( | |
<DesktopDateRangePicker | |
{...commonProps} | |
anchorEl={this._anchorRef.current} | |
onShortcutsChange={this._handleShortcutsChange} | |
onDoubleClick={this._handleDoubleClickToAccept} | |
disableClear={disableClear} | |
/> | |
) : ( | |
<MobileDateRangePicker {...commonProps} onClose={this._handleClickAway} /> | |
)} | |
</Box> | |
</ClickAwayListener> | |
); | |
} | |
_handleTextFieldClick = (e) => { | |
const { open } = this.state; | |
const isBubbleTriggered = this._inputRef.current !== e.target; | |
if (isBubbleTriggered) return; | |
this.setState({ | |
open: !open, | |
focused: true, | |
}); | |
}; | |
_handleCalendarIconClick = () => { | |
const { open } = this.state; | |
this.setState({ open: !open, focused: !open }); | |
}; | |
_handleEscPressed = (event) => { | |
if (event.key === 'Escape') { | |
event.preventDefault(); | |
this._handleClickAway(); | |
} | |
}; | |
_handleClose = () => { | |
this.setState({ | |
open: false, | |
}); | |
}; | |
_handleClickAway = () => { | |
if (this.state.open) { | |
this._handleAccept(null, true); | |
const activeElement = document.activeElement; | |
if ( | |
(activeElement.tagName === 'INPUT' || activeElement.tagName === 'BUTTON') && | |
activeElement !== this._inputRef.current | |
) { | |
this.setState({ | |
focused: false, | |
}); | |
} | |
} else { | |
this.setState({ focused: false }); | |
} | |
}; | |
_handleClearValue = () => { | |
this.setState({ | |
tempValue: [null, null], | |
}); | |
}; | |
_handleClearIconClick = (e) => { | |
const { onChange } = this.props; | |
e?.stopPropagation(); | |
this._handleClearValue(); | |
onChange?.([null, null]); | |
}; | |
_handleCancel = () => { | |
this.setState(initState); | |
}; | |
_handleDoubleClickToAccept = () => { | |
this._handleAccept(); | |
}; | |
_handleAccept = (newValueOrEvent, isClickAway) => { | |
const { tempValue } = this.state; | |
const newValue = Array.isArray(newValueOrEvent) && newValueOrEvent; | |
const { onChange } = this.props; | |
const valueToAccept = newValue || tempValue; | |
const isReadyToAccept = (Boolean(valueToAccept[0]) ^ Boolean(valueToAccept[1])) === 0; // => return 0 if (false ^ false) or (true ^ true) | |
if (!isReadyToAccept && isClickAway) { | |
this._handleCancel(); | |
} | |
if (typeof onChange !== 'function' || !isReadyToAccept) return; | |
this._handleClose(); | |
onChange(valueToAccept); | |
}; | |
_handlePreviousMonthClick = () => { | |
const { viewMonth } = this.state; | |
this.setState({ | |
viewMonth: moment(viewMonth || moment()).subtract(1, 'month'), | |
}); | |
}; | |
_renderDisplayText = (toLocaleText) => { | |
const { format, value } = this.props; | |
const { tempValue } = this.state; | |
const displayValue = tempValue[0] ? tempValue : value; | |
const [from, to] = displayValue; | |
const fromText = from ? moment(from).format(format) : ''; | |
const toText = to ? moment(to).format(format) : ''; | |
if (from) { | |
return `${fromText} ${toLocaleText} ${toText}`.trim(); | |
} | |
return ''; | |
}; | |
_handleNextMonthClick = () => { | |
const { viewMonth } = this.state; | |
this.setState({ | |
viewMonth: moment(viewMonth || moment()).add(1, 'month'), | |
}); | |
}; | |
_handleShortcutsChange = (newValue) => { | |
const { onChange, acceptOnShortcutClick } = this.props; | |
const [fromValue, toValue] = newValue; | |
// "shortcut item is [Reset]" | |
if (!fromValue || !toValue) { | |
acceptOnShortcutClick ? onChange?.([null, null]) : this._handleClearValue(); | |
} | |
// other shortcuts | |
else { | |
const [minDate, maxDate] = this._getSelectableRange(newValue); | |
const from = moment.max([minDate, fromValue].filter(Boolean)); | |
const minToDate = moment.min([toValue, maxDate].filter(Boolean)); | |
const to = minToDate.isBefore(from, 'day') ? from : minToDate; | |
const startDate = moment(from).startOf('day'); | |
const endDate = moment(to).endOf('day'); | |
acceptOnShortcutClick | |
? onChange?.([startDate, endDate]) | |
: this.setState({ | |
tempValue: [startDate, endDate], | |
}); | |
} | |
}; | |
_getRange = () => { | |
const { minDate, maxDate, disableFuture, disablePast } = this.props; | |
const now = moment(); | |
let validMinDate = minDate; | |
let validMaxDate = maxDate; | |
if (disableFuture) { | |
validMaxDate = maxDate ? moment.min(maxDate, now) : now; | |
} | |
if (disablePast) { | |
validMinDate = minDate ? moment.max(minDate, now) : now; | |
} | |
return [validMinDate, validMaxDate]; | |
}; | |
_getSelectableRange = (value) => { | |
const { maxRangeLength } = this.props; | |
const [fromDate, toDate] = value || []; | |
const [lowerBound, upperBound] = this._getRange(); | |
if (fromDate && !toDate && maxRangeLength) { | |
const startOfRange = moment(fromDate).subtract(maxRangeLength - 1, 'day'); | |
const minRangeDate = moment.max([lowerBound, startOfRange].filter(Boolean)); | |
const selectedDateInRange = moment.max([minRangeDate, fromDate].filter(Boolean)).endOf('day'); | |
const endOfRange = moment(selectedDateInRange).add(maxRangeLength - 1, 'day'); | |
const maxRangeDate = moment.min([endOfRange, upperBound].filter(Boolean)); | |
return [minRangeDate?.startOf('day'), maxRangeDate?.endOf('day')]; | |
} | |
return [lowerBound?.startOf('day'), upperBound?.endOf('day')]; | |
}; | |
_handleDateChange = (date) => { | |
const { matches: isDesktop } = this.props; | |
const { tempValue } = this.state; | |
const [fromDate, toDate] = tempValue; | |
const newValue = [fromDate, toDate]; | |
if (!fromDate) { | |
newValue[0] = date; | |
} else if (!toDate) { | |
if (date.isBefore(fromDate.startOf('d'))) { | |
newValue[0] = date; | |
} else { | |
newValue[1] = date; | |
} | |
} else { | |
newValue[0] = date; | |
newValue[1] = null; | |
} | |
this.setState( | |
{ | |
tempValue: newValue, | |
}, | |
isDesktop ? () => this._handleAccept(newValue) : null // accept when existed fromDate and toDate is selected | |
); | |
}; | |
} | |
DateRangePicker.propTypes = { | |
fullWidth: PropTypes.bool, | |
label: PropTypes.string, | |
helperText: PropTypes.string, | |
error: PropTypes.bool, | |
required: PropTypes.bool, | |
format: PropTypes.string, | |
disableClear: PropTypes.bool, | |
value: PropTypes.arrayOf(PropTypes.instanceOf(moment)), | |
onChange: PropTypes.func.isRequired, | |
minDate: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(moment)]), | |
maxDate: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(moment)]), | |
disableFuture: PropTypes.bool, | |
disablePast: PropTypes.bool, | |
maxRangeLength: PropTypes.number, | |
acceptOnShortcutClick: PropTypes.bool, | |
localeText: PropTypes.shape({ | |
to: PropTypes.string, | |
}), | |
}; | |
DateRangePicker.defaultProps = { | |
helperText: '', | |
error: false, | |
required: false, | |
disableClear: false, | |
format: Resources.FORMAT_DATE_DEFAULT, | |
minDate: moment('01/01/1900', Resources.FORMAT_DATE_DEFAULT), | |
maxDate: moment('01/01/2100', Resources.FORMAT_DATE_DEFAULT), | |
acceptOnShortcutClick: true, | |
localeText: { | |
to: 'đến', | |
}, | |
}; | |
export default withMediaQuery(DateRangePicker, { | |
mediaQueryString: DEFAULT_DESKTOP_MODE_MEDIA_QUERY, | |
defaultMatches: true, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment