react-native-calendars icon indicating copy to clipboard operation
react-native-calendars copied to clipboard

ExpandableCalendar initial position "open" is partially cut off in latest version (v1.1311.0)

Open juanmigdr opened this issue 8 months ago • 3 comments

Description:

In the latest version of react-native-calendars, setting the ExpandableCalendar's initial position to "open" does not fully display the calendar. Only part of the calendar appears, and it seems to be cut off visually.

This issue can be reproduced using one of the existing examples provided in the repo — specifically, the TimelineScreen example.

This behavior was not present in a previous version (1.1307.0) where the calendar would correctly render in its fully expanded state when the initialPosition prop was set to Positions.OPEN.

Code Sample: I just added the following to the example initialPosition={Positions.OPEN}

import groupBy from "lodash/groupBy";
import filter from "lodash/filter";
import find from "lodash/find";

import React, { Component } from "react";
import { Alert } from "react-native";
import {
  ExpandableCalendar,
  TimelineEventProps,
  TimelineList,
  CalendarProvider,
  TimelineProps,
  CalendarUtils,
} from "react-native-calendars";

import { timelineEvents, getDate } from "./timelineEvents";
import { Positions } from "react-native-calendars/src/expandableCalendar";

const INITIAL_TIME = { hour: 9, minutes: 0 };
const EVENTS: TimelineEventProps[] = timelineEvents;
export default class TimelineCalendarScreen extends Component {
  state = {
    currentDate: getDate(),
    events: EVENTS,
    eventsByDate: groupBy(EVENTS, (e) =>
      CalendarUtils.getCalendarDateString(e.start)
    ) as {
      [key: string]: TimelineEventProps[];
    },
  };

  marked = {
    [`${getDate(-1)}`]: { marked: true },
    [`${getDate()}`]: { marked: true },
    [`${getDate(1)}`]: { marked: true },
    [`${getDate(2)}`]: { marked: true },
    [`${getDate(4)}`]: { marked: true },
  };

  onDateChanged = (date: string, source: string) => {
    console.log("TimelineCalendarScreen onDateChanged: ", date, source);
    this.setState({ currentDate: date });
  };

  onMonthChange = (month: any, updateSource: any) => {
    console.log("TimelineCalendarScreen onMonthChange: ", month, updateSource);
  };

  createNewEvent: TimelineProps["onBackgroundLongPress"] = (
    timeString,
    timeObject
  ) => {
    const { eventsByDate } = this.state;
    const hourString = `${(timeObject.hour + 1).toString().padStart(2, "0")}`;
    const minutesString = `${timeObject.minutes.toString().padStart(2, "0")}`;

    const newEvent = {
      id: "draft",
      start: `${timeString}`,
      end: `${timeObject.date} ${hourString}:${minutesString}:00`,
      title: "New Event",
      color: "white",
    };

    if (timeObject.date) {
      if (eventsByDate[timeObject.date]) {
        eventsByDate[timeObject.date] = [
          ...eventsByDate[timeObject.date],
          newEvent,
        ];
        this.setState({ eventsByDate });
      } else {
        eventsByDate[timeObject.date] = [newEvent];
        this.setState({ eventsByDate: { ...eventsByDate } });
      }
    }
  };

  approveNewEvent: TimelineProps["onBackgroundLongPressOut"] = (
    _timeString,
    timeObject
  ) => {
    const { eventsByDate } = this.state;

    Alert.prompt("New Event", "Enter event title", [
      {
        text: "Cancel",
        onPress: () => {
          if (timeObject.date) {
            eventsByDate[timeObject.date] = filter(
              eventsByDate[timeObject.date],
              (e) => e.id !== "draft"
            );

            this.setState({
              eventsByDate,
            });
          }
        },
      },
      {
        text: "Create",
        onPress: (eventTitle) => {
          if (timeObject.date) {
            const draftEvent = find(eventsByDate[timeObject.date], {
              id: "draft",
            });
            if (draftEvent) {
              draftEvent.id = undefined;
              draftEvent.title = eventTitle ?? "New Event";
              draftEvent.color = "lightgreen";
              eventsByDate[timeObject.date] = [
                ...eventsByDate[timeObject.date],
              ];

              this.setState({
                eventsByDate,
              });
            }
          }
        },
      },
    ]);
  };

  private timelineProps: Partial<TimelineProps> = {
    format24h: true,
    onBackgroundLongPress: this.createNewEvent,
    onBackgroundLongPressOut: this.approveNewEvent,
    unavailableHours: [
      { start: 0, end: 6 },
      { start: 22, end: 24 },
    ],
    overlapEventsSpacing: 8,
    rightEdgeSpacing: 24,
  };

  render() {
    const { currentDate, eventsByDate } = this.state;

    return (
      <CalendarProvider
        date={currentDate}
        showTodayButton
        disabledOpacity={0.6}
      >
        <ExpandableCalendar
          firstDay={1}
          markedDates={this.marked}
          initialPosition={Positions.OPEN} // ONLY ADDED THIS LINE
        />
        <TimelineList
          events={eventsByDate}
          timelineProps={this.timelineProps}
          showNowIndicator
          scrollToFirst
          initialTime={INITIAL_TIME}
        />
      </CalendarProvider>
    );
  }
}

Please let me know if a fix or workaround is available for this regression.

juanmigdr avatar Apr 21 '25 11:04 juanmigdr

Hello i have the same bug with version 1.1311.0, does anyone know how to fix it please?

Image

sirine-berguiga avatar Apr 22 '25 12:04 sirine-berguiga

+1

I fixed it by patching it like this

// wrap the getOpenHeight in a useCallback and use it as a dependency 
const getOpenHeight = useCallback(() => {
        if (!horizontal) {
            return Math.max(constants.screenHeight, constants.screenWidth);
        }
        return headerHeight + (WEEK_HEIGHT * (numberOfWeeks.current)) + (hideKnob ? 0 : KNOB_CONTAINER_HEIGHT);
    }, [headerHeight, horizontal, hideKnob, numberOfWeeks]);

// always calculate with getOpenHeight()
 const startHeight = useMemo(() => isOpen ? getOpenHeight() : closedHeight, [isOpen, closedHeight, getOpenHeight]);

   useEffect(() => {
        _height.current = startHeight;
        deltaY.setValue(startHeight);
        // update wrapper height
        _wrapperStyles.current.style.height = startHeight;
    }, [startHeight]);

It seems to work pretty well

KimGnab avatar May 13 '25 11:05 KimGnab

+1 @KimGnab's patch worked for me!

Za-Sing avatar May 21 '25 18:05 Za-Sing

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 21 '25 23:08 stale[bot]