Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

independent multiple calendars option #13

Open
JHSeo-git opened this issue Feb 18, 2023 · 11 comments
Open

independent multiple calendars option #13

JHSeo-git opened this issue Feb 18, 2023 · 11 comments
Labels
enhancement New feature or request

Comments

@JHSeo-git
Copy link

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.

@Feshchenko Feshchenko added the enhancement New feature or request label Feb 18, 2023
@Feshchenko
Copy link
Contributor

@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 :)

@JHSeo-git
Copy link
Author

@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.

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.

@Feshchenko
Copy link
Contributor

Feshchenko commented Feb 19, 2023

@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 :)

@JHSeo-git
Copy link
Author

@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. :)

@huongdevvn
Copy link

huongdevvn commented Jul 11, 2023

@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:

Tab-1689058010605.webm

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,
    ),
  };
};

@JHSeo-git
Copy link
Author

JHSeo-git commented Jul 12, 2023

@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 };
}

@aelfannir
Copy link

Any update here @Feshchenko ?

@simontong
Copy link

Hi @JHSeo-git are you able to provide an example of how you would actually use this hook. Thanks!

@JHSeo-git
Copy link
Author

JHSeo-git commented Jan 29, 2024

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>
  );
};

@simontong
Copy link

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

1 similar comment
@simontong
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants