Skip to content

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

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

Open
juanmigdr opened this issue Apr 21, 2025 · 2 comments

Comments

@juanmigdr
Copy link

juanmigdr commented Apr 21, 2025

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.

@sirine-berguiga
Copy link

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

Image

@KimGnab
Copy link

KimGnab commented May 13, 2025

+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

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

No branches or pull requests

3 participants