MarceloPrado / flash-calendar

The fastest React Native calendar 📆⚡
https://marceloprado.github.io/flash-calendar/
MIT License
951 stars 27 forks source link

Remove some months from the render instead of mark them disabled #47

Open ioeldev opened 2 months ago

ioeldev commented 2 months ago

https://github.com/user-attachments/assets/d5c43a00-cdd2-40dd-8ca8-a8035a48f993

So in my <DateRangeInput /> component I have a regular input, that opens a Modal on click. The modal display the <Calendar.List /> component. I have added a row above the calendar that display the year, and two arrows back and forth to change the displayed months based on the selected year. currently when clicking on "previous year" or "next year" i am updating dynamically the calendarMaxDateId and calendarMinDateId with two states: maxDateId and minDateId

As you can see in the video when i update the calendarMaxDateId and calendarMinDateId, the months that are supposed to disappear get greyed out where i would like to remove them completely from rendering. Is there a way to do such ?

I can share my current code:

import { useEffect, useMemo, useRef, useState } from "react";
import { Box, SafeAreaBox, TextBox, TouchableOpacityBox } from "./CustomBoxes";
import { Button } from "./Button";
import { useTheme } from "../hooks/use-theme";
import { Modal } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import dayjs from "dayjs";
import { Input } from "./Input";
import {
  Calendar,
  useDateRange,
  CalendarListRef,
  toDateId,
  fromDateId,
} from "@marceloterreiro/flash-calendar";
import { useCalendarTheme } from "../hooks/use-calendar-theme";

export const DateRangeInput = ({
  startDate,
  endDate,
  mode,
  onChange,
  ...props
}: {
  startDate: string | undefined;
  endDate: string | undefined;
  onChange: (selectedStartDate: string, selectedEndDate: string) => void;
  mode: "date" | "time";
}) => {
  const { baseTheme } = useTheme();
  const [showDatePicker, setShowDatePicker] = useState(false);
  const { calendarActiveDateRanges, onCalendarDayPress, dateRange } =
    useDateRange();

  const { calendarTheme } = useCalendarTheme();
  const toggleDatePicker = () => {
    setShowDatePicker(!showDatePicker);
  };
  const calendarRef = useRef<CalendarListRef>(null);

  const handleScrollToToday = () => {
    calendarRef.current?.scrollToMonth(new Date(), true);
  };

  const [currentYear, setCurrentYear] = useState(dayjs().year());

  const handleNextYear = () => {
    calendarRef.current?.scrollToMonth(
      dayjs(`${currentYear + 1}-01-01`).toDate(),
      false
    );
    setCurrentYear(currentYear + 1);
  };

  const handlePrevYear = () => {
    calendarRef.current?.scrollToMonth(
      dayjs(`${currentYear - 1}-01-01`).toDate(),
      false
    );
    setCurrentYear(currentYear - 1);
  };

  const calendarPastScrollRangeInMonths = useMemo(() => {}, []);

  const calendarFutureScrollRangeInMonths = useMemo(() => {}, []);

  return (
    <>
      <Box flexDirection="row" alignItems="center" position="relative">
        <Ionicons
          name="calendar"
          size={18}
          color={baseTheme.colors.secondary}
          style={{ position: "absolute", zIndex: 100, left: 15 }}
        />
        <Input
          placeholder="Selectionner une date"
          readOnly
          value={
            startDate && endDate
              ? `${dayjs(startDate).format("DD/MM/YYYY")} - ${dayjs(endDate).format("DD/MM/YYYY")}`
              : ""
          }
          placeholderTextColor={baseTheme.colors.gray4}
          width={"100%"}
          pl="xl"
          style={{ fontSize: 16 }}
          onPress={toggleDatePicker}
          {...props}
        />
      </Box>

      <Modal
        visible={showDatePicker}
        transparent
        animationType="slide"
        onRequestClose={() => setShowDatePicker(false)}
      >
        <SafeAreaBox flex={1} bg="background">
          <Box justifyContent="flex-end" flexDirection="row" px="l" mb="l">
            <Ionicons name="close" size={30} onPress={toggleDatePicker} />
          </Box>

          <Box
            width={"100%"}
            flexDirection="row"
            justifyContent="space-between"
            alignItems="center"
            p="m"
          >
            <TouchableOpacityBox activeOpacity={0.8} onPress={handlePrevYear}>
              <Ionicons name="chevron-back" size={30} />
            </TouchableOpacityBox>

            <TouchableOpacityBox activeOpacity={0.8}>
              <TextBox>{currentYear}</TextBox>
            </TouchableOpacityBox>

            <TouchableOpacityBox activeOpacity={0.8} onPress={handleNextYear}>
              <Ionicons name="chevron-forward" size={30} />
            </TouchableOpacityBox>
          </Box>

          <Calendar.List
            ref={calendarRef}
            calendarMaxDateId={toDateId(dayjs(`${currentYear}-12-31`).toDate())}
            calendarMinDateId={toDateId(dayjs(`${currentYear}-01-01`).toDate())}
            calendarPastScrollRangeInMonths={calendarPastScrollRangeInMonths}
            calendarFutureScrollRangeInMonths={
              calendarFutureScrollRangeInMonths
            }
            showsVerticalScrollIndicator={true}
            calendarActiveDateRanges={calendarActiveDateRanges}
            onCalendarDayPress={(dateId: string) => {
              if (
                !dateRange.startId ||
                (dateRange.startId && dateRange.endId)
              ) {
                onChange(dateId, "");
              } else if (dateRange.startId && !dateRange.endId) {
                const isBeforeStartDate = dayjs(dateId).isBefore(
                  dateRange.startId
                );
                if (isBeforeStartDate) {
                  onChange(dateId, dateRange.startId); // Set new start date and reset end date
                } else {
                  onChange(dateRange.startId, dateId); // Set end date
                }
              }
              onCalendarDayPress(dateId);
            }}
            theme={calendarTheme}
          />
          <Box mt="xl" px="xl" pb="xl">
            <Button
              variant="filled"
              color="primary"
              onPress={() => {
                setShowDatePicker(false);
              }}
            >
              Done
            </Button>
          </Box>
        </SafeAreaBox>
      </Modal>
    </>
  );
};

Note that my calendarPastScrollRangeInMonths and calendarFutureScrollRangeInMonths memos have empty code since i tried multiple logics to achieve what i need but i can't figure out if it's possible. Thank you for your time !

ioeldev commented 2 months ago

Update:

Ok so for now i am removing the min and max date ids and just use the ref to scroll to the 01-01-${currentYear +/- 1} based on previous or next year. But i would want to display only the months of the related year in the future and if you can help me achieve that i would really appreciate !

My updated code:

export const DateRangeInput = ({
  startDate,
  endDate,
  mode,
  onChange,
  ...props
}: {
  startDate: string | undefined;
  endDate: string | undefined;
  onChange: (selectedStartDate: string, selectedEndDate: string) => void;
  mode: "date" | "time";
}) => {
  const { baseTheme } = useTheme();
  const [showDatePicker, setShowDatePicker] = useState(false);
  const { calendarActiveDateRanges, onCalendarDayPress, dateRange } =
    useDateRange();

  const { calendarTheme } = useCalendarTheme();
  const toggleDatePicker = () => {
    setShowDatePicker(!showDatePicker);
  };
  const calendarRef = useRef<CalendarListRef>(null);

  const handleScrollToToday = () => {
    calendarRef.current?.scrollToMonth(new Date(), true);
  };

  const [currentYear, setCurrentYear] = useState(dayjs().year());

  const handleNextYear = () => {
    calendarRef.current?.scrollToMonth(
      dayjs(`${currentYear + 1}-01-01`).toDate(),
      false
    );
    setCurrentYear(currentYear + 1);
  };

  const handlePrevYear = () => {
    calendarRef.current?.scrollToMonth(
      dayjs(`${currentYear - 1}-01-01`).toDate(),
      false
    );
    setCurrentYear(currentYear - 1);
  };

  return (
    <>
      <Box flexDirection="row" alignItems="center" position="relative">
        <Ionicons
          name="calendar"
          size={18}
          color={baseTheme.colors.secondary}
          style={{ position: "absolute", zIndex: 100, left: 15 }}
        />
        <Input
          placeholder="Selectionner une date"
          readOnly
          value={
            startDate && endDate
              ? `${dayjs(startDate).format("DD/MM/YYYY")} - ${dayjs(endDate).format("DD/MM/YYYY")}`
              : ""
          }
          placeholderTextColor={baseTheme.colors.gray4}
          width={"100%"}
          pl="xl"
          style={{ fontSize: 16 }}
          onPress={toggleDatePicker}
          {...props}
        />
      </Box>

      <Modal
        visible={showDatePicker}
        transparent
        animationType="slide"
        onRequestClose={() => setShowDatePicker(false)}
      >
        <SafeAreaBox flex={1} bg="background">
          <Box justifyContent="flex-end" flexDirection="row" px="l" mb="l">
            <Ionicons name="close" size={30} onPress={toggleDatePicker} />
          </Box>

          <Box
            width={"100%"}
            flexDirection="row"
            justifyContent="space-between"
            alignItems="center"
            p="m"
          >
            <TouchableOpacityBox activeOpacity={0.8} onPress={handlePrevYear}>
              <Ionicons name="chevron-back" size={30} />
            </TouchableOpacityBox>

            <TouchableOpacityBox activeOpacity={0.8}>
              <TextBox>{currentYear}</TextBox>
            </TouchableOpacityBox>

            <TouchableOpacityBox activeOpacity={0.8} onPress={handleNextYear}>
              <Ionicons name="chevron-forward" size={30} />
            </TouchableOpacityBox>
          </Box>

          <Calendar.List
            ref={calendarRef}
            showsVerticalScrollIndicator={true}
            calendarActiveDateRanges={calendarActiveDateRanges}
            onCalendarDayPress={onCalendarDayPress}
            theme={calendarTheme}
          />
          <Box mt="xl" px="xl" pb="xl">
            <Button
              variant="filled"
              color="primary"
              onPress={() => {
                setShowDatePicker(false);
              }}
            >
              Done
            </Button>
          </Box>
        </SafeAreaBox>
      </Modal>
    </>
  );
};
kirkalvar commented 1 month ago

This is how I limit the date range to display two months.

import React from 'react';
import {Calendar, useDateRange} from '@marceloterreiro/flash-calendar';

const today = '2024-08-12';
const LastDayOfNextMonth = '2024-09-30';

const CalendarPicker = (): React.JSX.Element => {
  const {
    calendarActiveDateRanges, 
    onCalendarDayPress, 
    dateRange
  } = useDateRange();

  console.log('Selected Dates:', dateRange);

  return (
    <Calendar.List
      calendarActiveDateRanges={calendarActiveDateRanges}
      calendarPastScrollRangeInMonths={0}
      calendarFutureScrollRangeInMonths={1}
      calendarMinDateId={today}
      calendarMaxDateId={LastDayOfNextMonth}
      calendarInitialMonthId={today}
      onCalendarDayPress={onCalendarDayPress}
    />
  );
};

export default CalendarPicker;