Skip to content

Improve calendar navigation UX with loading skeleton and optional sequential months#75

Merged
gin0115 merged 5 commits into
a8cteam51:trunkfrom
jbouganim:feature/calendar-filter
May 22, 2026
Merged

Improve calendar navigation UX with loading skeleton and optional sequential months#75
gin0115 merged 5 commits into
a8cteam51:trunkfrom
jbouganim:feature/calendar-filter

Conversation

@jbouganim
Copy link
Copy Markdown
Contributor

@jbouganim jbouganim commented May 21, 2026

Changes proposed in this Pull Request

  • Improve calendar UX during month navigation by adding a loading skeleton while calendar AJAX requests are in flight.
  • Fix mobile day selection by using viewport-based mobile detection (instead of CSS display checks that can be overridden by theme styles).
  • Add a new calendar block setting, Show Months in Order (sequentialMonths), to support sequential month-by-month navigation.
  • Keep backward compatibility by defaulting sequentialMonths to off for existing blocks.
  • Add simple_events_calendar_day_events filter to allow pre-render calendar day event customization (e.g. deduping).
  • Bump plugin version to 2.1.2 and add changelog notes in README.md.

Testing instructions

  • Add a Calendar block and verify month navigation shows a loading skeleton during request.
  • On mobile width (<768px), tap a day with events and confirm event list opens.
  • In block settings:
    • With Show Months in Order OFF, navigation skips to months with events.
    • With it ON, navigation moves month-by-month.
  • Confirm existing calendar blocks (without explicitly setting sequentialMonths) default to OFF behavior.

Mentions #
@gin0115 @tommusrhodus @ecairol - if one of you could review when you get a chance, we have a launch for ThePocketNYC on May 25 that would need these changes, ty!

Summary by CodeRabbit

  • New Features

    • Added "Show Months in Order" option for sequential calendar month navigation
    • Implemented a skeleton loading state for improved calendar loading experience
  • Bug Fixes

    • Fixed mobile day-selection logic for better touch interactions
  • Documentation

    • Added calendar event rendering docs including day-event deduplication
    • Updated changelog for version 2.1.2
  • Chores

    • Version bumped to 2.1.2

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8055cc1f-3cf9-4f32-bda7-e073633525b0

📥 Commits

Reviewing files that changed from the base of the PR and between 661b1e9 and ba65218.

📒 Files selected for processing (2)
  • src/classes/class-se-calendar.php
  • src/templates/calendar/calendar-skeleton.php

Walkthrough

This patch release (v2.1.2) adds sequential month navigation for calendars with sparse events, refactors mobile detection to use viewport width instead of DOM inspection, introduces a skeleton loading UI for better perceived performance, and exposes a filter hook for day-events deduplication. All manifest files are updated, a new block attribute controls sequential month behavior with backend support, new skeleton CSS and template handle loading states, and JavaScript event handling is refined for API-driven calendar updates.

Suggested reviewers

  • gin0115
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the two main UX improvements: loading skeleton for calendar navigation and the new sequential months feature, matching the primary changes in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/blocks/calendar/calendar.js (1)

204-231: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against out-of-order async responses during rapid navigation.

If users trigger multiple requests quickly, an older response can arrive last and overwrite newer calendar content. Add request sequencing (or abort previous requests) before calling updateContent.

💡 Suggested fix
 constructor() {
+	this.lastRequestId = 0;
 	this.DOM = {
   ...
 	};
 }

 sendCalendarRequest( date, calendarItem ) {
+	const requestId = ++this.lastRequestId;
 	this.showLoading( calendarItem );
 	apiFetch( {
 		path: '/simple-events/calendar',
 		method: 'POST',
 		data: {
 			date,
 			attributes,
 		},
 	} ).then( ( result ) => {
+		if ( requestId !== this.lastRequestId ) {
+			return;
+		}
 		if ( result.html ) {
 			this.updateContent( calendarItem, result.html );
 		} else {
 			console.log( result );
 		}
 	} )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/blocks/calendar/calendar.js` around lines 204 - 231, The
sendCalendarRequest method can be overwritten by out-of-order async responses;
add request sequencing by introducing a per-instance request counter (e.g.,
this._calendarRequestId) that you increment at the start of sendCalendarRequest,
capture the incremented id in the promise chain, and only call updateContent,
hideLoading, and initListeners if the captured id matches
this._calendarRequestId; alternatively store an AbortController (e.g.,
this._calendarAbortController) and abort any prior request before calling
apiFetch, then use the new controller.signal in apiFetch so only the latest
response is applied — implement the chosen approach inside sendCalendarRequest
around apiFetch and the promise callbacks to guard updateContent from stale
responses.
🧹 Nitpick comments (1)
src/blocks/calendar/style.scss (1)

81-88: ⚡ Quick win

Respect reduced-motion preference for shimmer animation.

Please disable the shimmer for users with prefers-reduced-motion: reduce to avoid motion-triggered discomfort.

♿ Suggested patch
 `@keyframes` simple-events-skeleton-shimmer {
 	0% {
 		background-position: 200% 0;
 	}
 	100% {
 		background-position: -200% 0;
 	}
 }
+
+@media (prefers-reduced-motion: reduce) {
+	.simple-events-calendar-skeleton__top-bar > span,
+	.simple-events-calendar-skeleton__header > span,
+	.simple-events-calendar-skeleton__day {
+		animation: none;
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/blocks/calendar/style.scss` around lines 81 - 88, The shimmer keyframes
(`@keyframes` simple-events-skeleton-shimmer) must respect the user's
reduced-motion preference: add a prefers-reduced-motion: reduce media query
alongside the keyframes (or immediately after) and disable the shimmer animation
there by setting the related skeleton element(s) to animation: none (or override
animation-name/animation-duration) so the simple-events-skeleton no longer
animates for users who opt into reduced motion. Ensure you target the same
selector(s) that use simple-events-skeleton-shimmer so the override takes
effect.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/blocks/calendar/calendar.js`:
- Around line 228-230: The current finally block calls
initListeners(calendarItem) after every request which rebinds global modal
handlers (handleModalFunctionality) and causes duplicate hover/toggle behavior;
update the code so global modal listeners are not re-attached on each
request—either call initListeners only once during component initialization (not
in the per-request finally), or modify initListeners/handleModalFunctionality to
check and early-return if listeners are already bound (e.g., a mounted flag)
before adding handlers; ensure hideLoading(calendarItem) still runs in finally
but move or guard the initListeners(calendarItem) invocation to prevent
duplicate binding.

---

Outside diff comments:
In `@src/blocks/calendar/calendar.js`:
- Around line 204-231: The sendCalendarRequest method can be overwritten by
out-of-order async responses; add request sequencing by introducing a
per-instance request counter (e.g., this._calendarRequestId) that you increment
at the start of sendCalendarRequest, capture the incremented id in the promise
chain, and only call updateContent, hideLoading, and initListeners if the
captured id matches this._calendarRequestId; alternatively store an
AbortController (e.g., this._calendarAbortController) and abort any prior
request before calling apiFetch, then use the new controller.signal in apiFetch
so only the latest response is applied — implement the chosen approach inside
sendCalendarRequest around apiFetch and the promise callbacks to guard
updateContent from stale responses.

---

Nitpick comments:
In `@src/blocks/calendar/style.scss`:
- Around line 81-88: The shimmer keyframes (`@keyframes`
simple-events-skeleton-shimmer) must respect the user's reduced-motion
preference: add a prefers-reduced-motion: reduce media query alongside the
keyframes (or immediately after) and disable the shimmer animation there by
setting the related skeleton element(s) to animation: none (or override
animation-name/animation-duration) so the simple-events-skeleton no longer
animates for users who opt into reduced motion. Ensure you target the same
selector(s) that use simple-events-skeleton-shimmer so the override takes
effect.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2d7b1d8c-e20d-495f-8525-8bd5ea2e6906

📥 Commits

Reviewing files that changed from the base of the PR and between 12bb8e8 and 661b1e9.

📒 Files selected for processing (11)
  • README.md
  • package.json
  • plugin.php
  • src/blocks/calendar/block.json
  • src/blocks/calendar/calendar.js
  • src/blocks/calendar/index.js
  • src/blocks/calendar/style.scss
  • src/classes/class-se-blocks.php
  • src/classes/class-se-calendar.php
  • src/templates/calendar/calendar-container.php
  • src/templates/calendar/calendar-skeleton.php

Comment on lines +228 to +230
.finally( () => {
this.hideLoading( calendarItem );
this.initListeners( calendarItem );
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid rebinding global modal listeners on every request completion.

Line 230 calls initListeners() in finally, which also runs handleModalFunctionality() globally. This can stack duplicate hover handlers after repeated navigation and cause multiple modal toggles per interaction.

💡 Suggested fix
-			.finally( () => {
-				this.hideLoading( calendarItem );
-				this.initListeners( calendarItem );
-			} );
+			.finally( () => {
+				this.hideLoading( calendarItem );
+				// Rebind only listeners tied to replaced calendar markup.
+				this.addNavigationItemsListeners( calendarItem );
+				this.addCalendarDayListeners( calendarItem );
+				// Keep modal binding scoped/idempotent (adjust handleModalFunctionality separately).
+			} );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/blocks/calendar/calendar.js` around lines 228 - 230, The current finally
block calls initListeners(calendarItem) after every request which rebinds global
modal handlers (handleModalFunctionality) and causes duplicate hover/toggle
behavior; update the code so global modal listeners are not re-attached on each
request—either call initListeners only once during component initialization (not
in the per-request finally), or modify initListeners/handleModalFunctionality to
check and early-return if listeners are already bound (e.g., a mounted flag)
before adding handlers; ensure hideLoading(calendarItem) still runs in finally
but move or guard the initListeners(calendarItem) invocation to prevent
duplicate binding.

@gin0115 gin0115 merged commit 1289ce6 into a8cteam51:trunk May 22, 2026
8 checks passed
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

Successfully merging this pull request may close these issues.

2 participants