vanilla-calendar-pro icon indicating copy to clipboard operation
vanilla-calendar-pro copied to clipboard

Responsive Layout - pop-up negatively interfering

Open uvarov-frontend opened this issue 11 months ago • 4 comments

Discussed in https://github.com/uvarov-frontend/vanilla-calendar-pro/discussions/176

Originally posted by pablopereira27 December 30, 2023 For small screens (Boostrap sm ceil breakpoint = 767.98px), I applied the following change to the multiple month calendar.

@media (max-width: 767.98px) { .vanilla-calendar_multiple { width: 100%; max-width: 550px; } }

If it weren't for the pop-ups that can go out the calendar, it would be great.

image image

overflow: hidden in the .calendar prevents the entire site from being affected, but prevents the user from seeing pop-ups that exceed the calendar.

image image

I couldn't find a way to solve it via CSS. Could you help me with any tips or any future adjustments to the calendar javascript to avoid this leak of calendar pop-ups?

uvarov-frontend avatar Mar 11 '24 11:03 uvarov-frontend

Hi @uvarov-frontend,

I encountered an issue where the entire calendar (not just a popup) was overflowing outside the body container at a specific breakpoint (1070px).

I tried fixing it with CSS initially, but wasn't successful. I then implemented a JavaScript solution that resolved the problem.

See the original issue in this example: website (1070px breakpoint).

Here's the fixed version for your reference: website

Here is the JavaScript code I used for the fix.

const options = {
  input: true,
  actions: {
    changeToInput(e, calendar, self) {
      if (!self.HTMLInputElement) return;
      if (self.selectedDates[0]) {
        self.HTMLInputElement.innerHTML = self.selectedDates[0];

        // if you want to hide the calendar after picking a date
        calendar.hide();
      } else {
        self.HTMLInputElement.innerHTML = "Select Date";
      }
    },
    showCalendar(self) {
      const setPositionCalendar = (
        input,
        calendar,
        position,
        css,
      ) => {
        const getPosition = {
          top: -calendar.offsetHeight,
          bottom: input.offsetHeight,
          left: 0,
          center: input.offsetWidth / 2 - calendar.offsetWidth / 2,
          right: input.offsetWidth - calendar.offsetWidth,
        };

        const YPosition = !Array.isArray(position)
          ? "bottom"
          : position[0];
        const XPosition = !Array.isArray(position)
          ? position
          : position[1];

        let left = input.offsetLeft;
        let top = input.offsetTop;

        // Calculate document dimensions
        const docWidth = document.documentElement.clientWidth;
        const docHeight = document.documentElement.clientHeight;

        // Calculate window scroll offsets
        const scrollLeft =
          window.scrollX || document.documentElement.scrollLeft;
        const scrollTop =
          window.scrollY || document.documentElement.scrollTop;

        // Calculate calendar width and set maximum width to 100%
        const calendarWidth = Math.min(
          calendar.offsetWidth,
          docWidth,
        );

        // Check if there's horizontal overflow
        const rightOverflow =
          left + calendarWidth > docWidth + scrollLeft;
        const leftOverflow = left < scrollLeft;

        // Check if there's vertical overflow
        const verticalOverflow =
          (YPosition === "bottom" &&
            top + input.offsetHeight + calendar.offsetHeight >
              docHeight + scrollTop) ||
          (YPosition === "top" &&
            top - calendar.offsetHeight < scrollTop);

        // Adjust positions accordingly
        if (rightOverflow && XPosition !== "left") {
          left = input.offsetLeft + input.offsetWidth - calendarWidth;
        } else if (leftOverflow && XPosition !== "right") {
          left = input.offsetLeft;
        }

        // Center the calendar if the left or right value is negative
        if (left < 0 || left + calendarWidth > docWidth) {
          left =
            document.documentElement.offsetLeft +
            (document.documentElement.offsetWidth - calendarWidth) /
              2;
        }

        if (verticalOverflow) {
          top =
            YPosition === "bottom"
              ? input.offsetTop - calendar.offsetHeight
              : input.offsetTop + input.offsetHeight;
        } else {
          top =
            YPosition === "bottom"
              ? top + input.offsetHeight
              : top - calendar.offsetHeight;
        }

        calendar.classList.add(
          YPosition === "bottom"
            ? css.calendarToInputBottom
            : css.calendarToInputTop,
        );

        Object.assign(calendar.style, {
          left: `${left}px`,
          top: `${top}px`,
          maxWidth: "100%",
        });
      };

      setPositionCalendar(
        self.HTMLInputElement,
        self.HTMLElement,
        "auto",
        self.CSSClasses,
      );

      const actionsInput = (self) => ({
        hide() {
          self.HTMLElement.classList.add(
            self.CSSClasses.calendarHidden,
          );
          if (self.actions.hideCalendar)
            self.actions.hideCalendar(self);
        },
        show() {
          self.HTMLElement.classList.remove(
            self.CSSClasses.calendarHidden,
          );
          if (self.actions.showCalendar)
            self.actions.showCalendar(self);
        },
        self,
      });

      const documentClickEvent = (e) => {
        if (
          !self ||
          e.target === self.HTMLInputElement ||
          self.HTMLElement?.contains(e.target)
        )
          return;

        if (self.HTMLInputElement && self.HTMLElement)
          actionsInput(self).hide();
        window.removeEventListener("resize", handleResize);
        document.removeEventListener("click", documentClickEvent, {
          capture: true,
        });
      };

      const handleResize = () =>
        setPositionCalendar(
          self.HTMLInputElement,
          self.HTMLElement,
          "auto",
          self.CSSClasses,
        );

      self.HTMLInputElement.addEventListener("click", () => {
        window.addEventListener("resize", handleResize);
        document.addEventListener("click", documentClickEvent, {
          capture: true,
        });
      });

      document.addEventListener("click", documentClickEvent, {
        capture: true,
      });

      window.addEventListener("resize", handleResize);
      window.addEventListener("scroll", handleResize);
    },
  },

tfsumon avatar Mar 13 '24 08:03 tfsumon

Probably easier to use Floating UI for positioning.

whataboutpereira avatar Mar 13 '24 08:03 whataboutpereira

@tfsumon Thanks for sharing, I’ll think about making the calendar auto-position depending on the breakpoint. But it was easier for you to use Floating UI, as already written above.

uvarov-frontend avatar Mar 19 '24 12:03 uvarov-frontend

I have the code for auto-positioning in a fork, I can submit a PR for auto-positioning after all my other PRs are reviewed/merged :)

I also assume that my code could help with the original issue since I have a new function that given an element, will calculate the best possible side to reposition itself (by available space depending on element's position in the viewport).

Cheers

ghiscoding avatar May 23 '24 20:05 ghiscoding

I have the code for auto-positioning in a fork, I can submit a PR for auto-positioning after all my other PRs are reviewed/merged :)

I also assume that my code could help with the original issue since I have a new function that given an element, will calculate the best possible side to reposition itself (by available space depending on element's position in the viewport).

Cheers

Your PRs are merged, thank you for your help in supporting the project!

uvarov-frontend avatar Jul 28 '24 09:07 uvarov-frontend

So after creating new open PR #278, I started to play with that auto-position code to see if I can fix the issue mentioned here with the pop-ups and I'm getting close to a solution to auto-position not just the calendar picker but also these pop-ups. However 1 noticeable problem that I see with my fix is that there's an arrow in the top center position of the pop-up and that middle arrow becomes quite offset with the day element that we're hovering when it is used with my auto-position fix.

The question though is, do we really need this arrow pointer? I would personally just go without it. I think that if we provide this auto-position of the pop-up, we should maybe disable or remove this pop-up arrow pointer completely. @uvarov-frontend what do you think I should do? Perhaps, an extra CSS class like .with-arrow to go with/without the arrow. But anyway, I can't proceed without PR #278 to be reviewed first.

The arrow CSS positioned exactly set in the middle via this piece of code

https://github.com/uvarov-frontend/vanilla-calendar-pro/blob/501f311faf141414acfd7f9bf17ea52490a15dff/package/src/styles/vanilla-calendar.layout.css#L232-L236

As you can see below, my auto-position of the pop-up seems to work well, except for that arrow that no longer pointing to the correct position.

brave_xZ5v91UvbI

and just for comparison, below is without the auto-positioning fix that I'm working on

image

ghiscoding avatar Jul 30 '24 05:07 ghiscoding

So after creating new open PR #278, I started to play with that auto-position code to see if I can fix the issue mentioned here with the pop-ups and I'm getting close to a solution to auto-position not just the calendar picker but also these pop-ups. However 1 noticeable problem that I see with my fix is that there's an arrow in the top center position of the pop-up and that middle arrow becomes quite offset with the day element that we're hovering when it is used with my auto-position fix.

The question though is, do we really need this arrow pointer? I would personally just go without it. I think that if we provide this auto-position of the pop-up, we should maybe disable or remove this pop-up arrow pointer completely. @uvarov-frontend what do you think I should do? Perhaps, an extra CSS class like .with-arrow to go with/without the arrow. But anyway, I can't proceed without PR #278 to be reviewed first.

The arrow CSS positioned exactly set in the middle via this piece of code

https://github.com/uvarov-frontend/vanilla-calendar-pro/blob/501f311faf141414acfd7f9bf17ea52490a15dff/package/src/styles/vanilla-calendar.layout.css#L232-L236

As you can see below, my auto-position of the pop-up seems to work well, except for that arrow that no longer pointing to the correct position.

Need to remove the arrow if the window is auto-positioned, or offset this arrow. Add a PR as it was done in the first animation, I will add a couple of commits for this arrow.

uvarov-frontend avatar Aug 03 '24 09:08 uvarov-frontend