Reputation: 2084
I'm trying to build a DateRangePicker using MUI RangePickers, but I don't know how to use one only calendar to select the start date and the end date.
This is what I tried:
https://codesandbox.io/s/competent-wescoff-759vfm?file=/src/App.tsx
I'm not using the DateRangePicker from mui-x-pro, since it's only available in pro plan.
How can I make the two inputs to use one single calendar to select a range ?
Upvotes: 3
Views: 13637
Reputation: 11
I know this is a very old query, for solving this issue i found a package,
https://www.npmjs.com/package/react-quick-date-range-picker
see demo: https://stackblitz.com/edit/vitejs-vite-zrmeum?file=src%2Findex.css
Or you can use the code from https://github.com/HarshithKumar-A/react-quick-date-range-picker
import React from "react";
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import {
DatePicker,
DatePickerProps,
PickerValidDate,
} from "@mui/x-date-pickers";
import moment, { Moment } from "moment";
import { useEffect, useState } from "react";
import CustomCalendarHeader from "./components/CustomCalendarHeader";
import Layout from "./components/CustomCalenderLayout";
import Day from "./components/CustomCalenderDay";
import { DateRangePickerStyled } from "./styled";
moment.updateLocale("en", {
week: {
dow: 1,
},
});
export type DateRange = [Moment | null, Moment | null];
interface DateRangePickerProps
extends Omit<
DatePickerProps<PickerValidDate, boolean>,
"onChange" | "value"
> {
value: DateRange | null;
onChange: (value: DateRange | null) => void;
}
const CustomDatePicker = ({
value,
onChange,
...restProps
}: DateRangePickerProps) => {
const [startDate, setStartDate] = useState<Moment | null>(value?.[0] || null);
const [endDate, setEndDate] = useState<Moment | null>(value?.[1] || null);
const [open, setOpen] = useState(false);
const isInRange = (date: Moment): boolean => {
if (!startDate || !endDate) return false;
return date.isBetween(startDate, endDate, "day", "[]");
};
const selectAndCloseCalendar = (start: Moment | null, end: Moment | null) => {
if (start && !end) {
end = start.clone();
}
onChange([start, end]);
setOpen(false);
};
const handleToolbarAction = (
start: Moment | null,
end: Moment | null,
action: string
) => {
setStartDate(start);
setEndDate(end);
if (action !== "reset") {
selectAndCloseCalendar(start, end);
}
};
const handleDateChange = (date: Moment | null) => {
if (!startDate || endDate || (date && date.isBefore(startDate, "day"))) {
setStartDate(date);
setEndDate(null);
} else {
setEndDate(date);
selectAndCloseCalendar(startDate, date);
}
};
useEffect(() => {
if (value) {
setStartDate(value[0]);
setEndDate(value[1]);
}
}, [value]);
return (
<DateRangePickerStyled>
<LocalizationProvider dateAdapter={AdapterMoment}>
<DatePicker
views={["month", "year", "day"]}
reduceAnimations
value={endDate || startDate || null}
closeOnSelect={false}
disableHighlightToday
open={open}
onOpen={() => setOpen(true)}
onClose={() => selectAndCloseCalendar(startDate, endDate)}
showDaysOutsideCurrentMonth
dayOfWeekFormatter={(day) =>
["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"][day.day()]
}
slots={{
day: (day) => (
<Day
isInRange={isInRange}
startDate={startDate}
endDate={endDate}
onDateClick={handleDateChange}
{...day}
/>
),
calendarHeader: (props) => (
<CustomCalendarHeader
date={props.currentMonth}
onMonthChange={props.onMonthChange}
onViewChange={props.onViewChange}
/>
),
layout: (prop) => (
<Layout
handleToolbarAction={handleToolbarAction}
startDate={startDate}
endDate={endDate}
>
{prop.children}
</Layout>
),
}}
{...restProps}
/>
</LocalizationProvider>
</DateRangePickerStyled>
);
};
export default CustomDatePicker;
Upvotes: 0
Reputation: 601
If I understand you correctly you want one calendar where you can press a start date and an end date and then these are highlighted. I have solved this but I haven't done much about the design so that will be up to you. I have solved the problem using the DatePicker
elements renderDay
property.
import * as React from 'react';
import TextField, { TextFieldProps } from '@mui/material/TextField';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker';
import styled from '@emotion/styled';
import { PickersDay } from '@mui/x-date-pickers/PickersDay';
const DateRangePickerStyled = styled('div')(() => ({
display: 'flex',
alignItems: 'center',
}));
const DateRangePicker = (props: DateRangePickerProps) => {
const { value, onChange, ...rest } = props;
const [startDate, setStartDate] = React.useState(0);
const [endDate, setEndDate] = React.useState(0);
const [datesPicked, setDatesPicked] = React.useState(0);
return (
<DateRangePickerStyled>
<DatePicker
value={new Date()}
minDate={startDate}
onChange={(date: any) => {
setDatesPicked(datesPicked + 1);
if (datesPicked % 2 !== 0) {
setEndDate(date.$D);
} else {
setStartDate(date.$D);
setEndDate(0);
}
}}
closeOnSelect={false}
renderDay={(day, _value, DayComponentProps) => {
const isSelected =
!DayComponentProps.outsideCurrentMonth &&
Array.from(
{ length: endDate - startDate + 2 },
(x, i) => i + startDate - 1
).indexOf(day.date()) > 0;
return (
<div
style={
isSelected
? {
backgroundColor: 'blue',
}
: {}
}
key={day.toString()}
>
<PickersDay {...DayComponentProps} />
</div>
);
}}
{...rest}
/>
</DateRangePickerStyled>
);
};
export default function MaterialUIPickers() {
const [value, setValue] = React.useState<DateRangePickerValueType | null>(
null
);
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateRangePicker
value={value}
onChange={(newValue) => {
setValue(newValue as DateRangePickerValueType);
}}
renderInput={(params: TextFieldProps) => (
<TextField {...params} />
)}
/>
</LocalizationProvider>
);
}
type DateRangePickerValueType = {
start: unknown;
end: unknown;
};
interface DateRangePickerProps
extends Omit<DatePickerProps<unknown, unknown>, 'value'> {
value: DateRangePickerValueType | null;
}
More examples of this method can be found on:
https://mui.com/x/react-date-pickers/date-picker/
If you also want to limit which dates are selectable I would suggest using the minDate
property.
If I've misunderstood what it is you wanted or my solution is not all the way there feel free to leave a comment and I'll take an extra look, hope it helps :-)
Upvotes: 3