datepicker
datepicker copied to clipboard
independent multiple calendars option
First, I'd like to say that I'm enjoying using the library. 😄
I have a small question about additional functionality for range mode.
There is currently no method to select a date through multiple independent calendars when using range mode. We can use offset to show multiple calendars, but they are sequential and not independent.
(like will-be-in-range
state when hover, days when move prev month / next month, ...)
In order to use independent calendars in RANGE mode, I have personally implemented a few methods in my limited skills.
If you could provide this functionality in the library, I think it could be applied in many cases.
Once again, thank you for creating such an awesome library. 👍
Here is the sample code and application I did.
- sample: https://practice-storybook-vite.pages.dev/?path=/story/components-datepicker--range-template
- code: https://github.com/JHSeo-git/practice-storybook-vite/blob/8c9528b671a2b0b35d7ee0f95fba0c601372007e/src/components/DatePicker/DatePicker.Range.tsx#L69
@JHSeo-git, thanks for reaching out with this proposal. Your examples look great, firstly I haven't realised that this is my lib :) I will think about how to make it happen, but at the same time it could be weird when you select 2 same months and try to select something :)
@Feshchenko Thank you for your kind comment.
You're right, it can look weird when selecting for the same month. So I looked up some references. here is the one of them I liked.
- https://rsuitejs.com/components/date-range-picker/#basic
The idea here is to remove the choice of the same month appearing in 2 calendars from the options.
The maxMonth of the start-range calendar is always one month less than the end-range calendar, MinMonth for the end-range calendar is always one month greater than the start-range calendar.
I think this is something you might want to consider, so here's a little suggestion! Thank you for your interest in this.
@JHSeo-git thanks for the reference. It is easy to understand how to do this with 2 calendars. But what if you have 3 or 5 🤪 It is nice thing to think of in the future :)
@Feshchenko Oh that's right, I hadn't thought about that a bit more. 🤔 Thanks for the kind words, even though it's a weak idea. :)
@JHSeo-git I want create a date picker same as your. After working on it for several days, I was able to develop a date picker similar to what you wanted. However, I had to implement several workarounds to achieve the desired functionality. In order to make it work smoothly and better clean code, I ended up forking the repository and adding an offsetDay
parameter to the configuration. Here is a demo of my date picker:
This is my changes of create-initial-state.ts
in forked repo:
const {
selectedDates,
offsetDate,
focusDate,
dates: { minDate, maxDate },
years,
} = config;
let initialOffsetDate = undefined;
if (offsetDate) {
initialOffsetDate = getCalendarStartDate(
minDate,
maxDate,
getCleanDate(offsetDate),
);
} else {
initialOffsetDate =
selectedDates.length > 0
? selectedDates[selectedDates.length - 1]
: getCalendarStartDate(minDate, maxDate, getCleanDate(newDate()));
}
return {
focusDate,
rangeEnd: null,
offsetDate: initialOffsetDate,
offsetYear: getCurrentYearPosition(
getDateParts(initialOffsetDate).Y,
years,
),
};
};
@huongdevvn Wow That looks good! appreciate your comment. 😄
Similarly, I created a custom hook for a similar functionality It's not optimized code, but it works well for cases, so that's what I use for now.
Here's that code.
/* -------------------------------------------------------------------------------------------------
* useRangeCalendarOffset
* -----------------------------------------------------------------------------------------------*/
type RangeType = 'start' | 'end';
interface UseRangeCalendarOffsetProps {
startDpState: DPState;
endDpState: DPState;
range: RangeType;
}
function useRangeCalendarOffset({ startDpState, endDpState, range }: UseRangeCalendarOffsetProps) {
const startCalendars = useCalendars(startDpState);
const endCalendars = useCalendars(endDpState);
const startCalendar = startCalendars.calendars[0];
const endCalendar = endCalendars.calendars[0];
const startRangeCalendar = React.useMemo(
() => ({
year: parseInt(getNumericText(startCalendar.year)),
month: parseInt(getNumericText(startCalendar.month)),
}),
[startCalendar]
);
const endRangeCalendar = React.useMemo(
() => ({
year: parseInt(getNumericText(endCalendar.year)),
month: parseInt(getNumericText(endCalendar.month)),
}),
[endCalendar]
);
const { dispatch: startDpDispatch } = startDpState;
const { dispatch: endDpDispatch } = endDpState;
const onCalculateDayOffset = React.useCallback(() => {
const startDate = new Date(startRangeCalendar.year, startRangeCalendar.month - 1, 1);
const endDate = new Date(endRangeCalendar.year, endRangeCalendar.month - 1, 1);
if (range === 'start') {
const nextDate = addMonths(startDate, 1);
if (endDate <= nextDate) {
endDpDispatch({
type: 'SET_OFFSET_DATE',
date: addMonths(nextDate, 1),
});
return;
}
}
if (range === 'end') {
const nextDate = subMonths(endDate, 1);
if (startDate >= nextDate) {
startDpDispatch({
type: 'SET_OFFSET_DATE',
date: subMonths(nextDate, 1),
});
return;
}
}
}, [startRangeCalendar, endRangeCalendar, startDpDispatch, endDpDispatch, range]);
const onCalculateMonthOffset = React.useCallback(
(m: CalendarMonth) => {
const startDate = new Date(startRangeCalendar.year, startRangeCalendar.month - 1, 1);
const endDate = new Date(endRangeCalendar.year, endRangeCalendar.month - 1, 1);
if (range === 'start') {
const nextDate = m.$date;
if (endDate <= nextDate) {
endDpDispatch({
type: 'SET_OFFSET_DATE',
date: addMonths(nextDate, 1),
});
return;
}
}
if (range === 'end') {
const nextDate = m.$date;
if (startDate >= nextDate) {
startDpDispatch({
type: 'SET_OFFSET_DATE',
date: subMonths(nextDate, 1),
});
return;
}
}
},
[startRangeCalendar, endRangeCalendar, startDpDispatch, endDpDispatch, range]
);
const onCalculateYearOffset = React.useCallback(
(y: CalendarYear) => {
const startDate = new Date(startRangeCalendar.year, startRangeCalendar.month - 1, 1);
const endDate = new Date(endRangeCalendar.year, endRangeCalendar.month - 1, 1);
if (range === 'start') {
const nextDate = y.$date;
if (endDate <= nextDate) {
endDpDispatch({
type: 'SET_OFFSET_DATE',
date: addMonths(nextDate, 1),
});
return;
}
}
if (range === 'end') {
const nextDate = y.$date;
if (startDate >= nextDate) {
startDpDispatch({
type: 'SET_OFFSET_DATE',
date: subMonths(nextDate, 1),
});
return;
}
}
},
[startRangeCalendar, endRangeCalendar, startDpDispatch, endDpDispatch, range]
);
return { onCalculateDayOffset, onCalculateMonthOffset, onCalculateYearOffset };
}
Any update here @Feshchenko ?
Hi @JHSeo-git are you able to provide an example of how you would actually use this hook. Thanks!
Hi @JHSeo-git are you able to provide an example of how you would actually use this hook. Thanks!
@simontong Where I used that hook was to naturally change the start and end dates like a rsuit datepicker when an event occurs in day, month, and year mode, so here's what I used (not exactly the same, but closely)
interface CalendarRangeProps {
startDpState: DPState;
endDpState: DPState;
range: 'start' | 'end';
}
const CalendarRange = ({ startDpState, endDpState, range }: CalendarRangeProps) => {
const targetDpState = range === 'start' ? startDpState : endDpState;
const [calendarViewMode, setCalendarViewMode] = useCalendarViewMode();
const { onCalculateDayOffset, onCalculateMonthOffset, onCalculateYearOffset } =
userRangeCalendarOffset({ startDpState, endDpState, range });
React.useLayoutEffect(() => {
const startDate = startDpState.selectedDates[0];
if (startDate) {
startDpState.dispatch({ type: 'SET_OFFSET_DATE', date: startDate });
}
if (!endDate) {
endDpState.dispatch({ type: 'SET_OFFSET_DATE', date: addMonths(startDate ?? today, 1) });
}
if (startDate?.getMonth() <= endDate?.getMonth()) {
endDpState.dispatch({ type: 'SET_OFFSET_DATE', date: addMonths(startDate ?? today, 1) });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div>
<CalendarHeader
dpState={range === 'start' ? startDpState : endDpState}
calendarViewMode={calendarViewMode}
setCalendarViewMode={setCalendarViewMode}
onPrevClick={range === 'end' ? onCalculateDayOffset : undefined}
onNextClick={range === 'start' ? onCalculateDayOffset : undefined}
/>
<div className="mt-2">
{calendarViewMode === 'days' && (
<CalendarRangeDays startDpState={startDpState} endDpState={endDpState} range={range} />
)}
{calendarViewMode === 'months' && (
<CalendarMonths
dpState={targetDpState}
setCalendarViewMode={setCalendarViewMode}
onMonthClick={onCalculateMonthOffset}
/>
)}
{calendarViewMode === 'years' && (
<CalendarYears
dpState={targetDpState}
setCalendarViewMode={setCalendarViewMode}
onYearClick={onCalculateYearOffset}
/>
)}
</div>
</div>
);
};
Is it possible to provide a more complete example that shows how the onCalculateDayOffset, onCalculateMonthOffset, onCalculateYearOffset are used, I was unable to get this to work. Thanks
Is it possible to provide a more complete example that shows how the onCalculateDayOffset, onCalculateMonthOffset, onCalculateYearOffset are used, I was unable to get this to work. Thanks