Skip to content

[WC-3313]: Calendar Year View#2353

Open
rahmanunver wants to merge 5 commits into
mainfrom
calendar/year-view
Open

[WC-3313]: Calendar Year View#2353
rahmanunver wants to merge 5 commits into
mainfrom
calendar/year-view

Conversation

@rahmanunver

@rahmanunver rahmanunver commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What

Adds a Year view to the Calendar widget: a 12-month grid with event-indicator dots and prev/next/today year navigation, available in both Standard and Custom view modes.

Configurable day-click target

Clicking a day drills into an author-configurable target view (yearDayClickView: day / week / work_week / month / agenda, default day). The target is constrained at runtime to views that are actually enabled — if the chosen target isn't enabled, day cells render non-interactive (no role/tabIndex/handlers, aria-label retained) and nothing navigates.

Also in this PR

  • fix: declare .css/.scss/.png module types (typings/modules.d.ts) to satisfy tsc — matches the sibling-widget convention.
  • chore: moved calendar's OpenSpec change into the package (calendar-web/openspec/), adopting the package-scoped OpenSpec model.

@github-actions

This comment has been minimized.

@rahmanunver rahmanunver changed the title feat(calendar-web): year view with configurable day-click target [WC-3313]: Calendar Year View Jul 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
src/components/MonthMiniGrid.tsx New component — month mini-grid with day cells, event dots, interactive/non-interactive modes
src/components/YearView.tsx New component — 12-month grid container with event grouping by month
src/helpers/YearViewController.ts New helper — react-big-calendar custom view statics (navigate/title/range) as plain object
src/helpers/CalendarPropsBuilder.ts Extended — getEnabledViewNames(), resolveDayClickView(), year view registration
src/components/Toolbar.tsx Extended — year added to ResolvedToolbarItem, caption fallback for custom view
src/Calendar.xml Extended — year enum values, yearDayClickViewStandard/yearDayClickViewCustom properties
typings/CalendarProps.d.ts Extended — new enums and props for year and day-click target
src/Calendar.editorConfig.ts Extended — mode-specific hiding for new day-click properties
src/Calendar.editorPreview.tsx Extended — year included in preview views array
src/ui/Calendar.scss Extended — year view styles nested under .widget-calendar
src/utils/calendar-utils.ts Extended — additional date-fns re-exports
src/helpers/useCalendarEvents.ts Import order only
src/helpers/useLocalizer.ts Import order only
src/Calendar.tsx Import order only
typings/modules.d.ts New — CSS/SCSS/PNG module type declarations
src/__tests__/Calendar.spec.tsx Extended — phantom-day and configurable-target tests
src/components/__tests__/MonthMiniGrid.spec.tsx New — full cell/event/interaction coverage
src/components/__tests__/Toolbar.spec.tsx New — year button caption + active state
src/components/__tests__/YearView.spec.tsx New — event grouping, year-boundary, drill-down wiring
src/helpers/__tests__/YearViewController.spec.ts New — navigate/title/range/getComponent
CHANGELOG.md Added unreleased entry
openspec/** Planning artifacts — not reviewed
rich-text-web/typings/RichTextProps.d.ts Import order only (generated file, unrelated to this PR)

Skipped (out of scope): dist/, pnpm-lock.yaml, openspec/ planning docs


Findings

🔶 Medium — Incorrect aria-label month and year for leading/trailing day cells

File: packages/pluggableWidgets/calendar-web/src/components/MonthMiniGrid.tsxariaLabel construction inside dayCells.map

Problem: The aria-label for every cell is built with monthName (the current grid's month name) and year (the current grid's year), even for leading days (from the previous month) and trailing days (from the next month). A leading cell for Dec 31 in a January 2026 grid announces "Jan 31, 2026, no events" instead of "Dec 31, 2025, no events". Year is also wrong for January leading days and December trailing days. The existing test only covers current-month cells on a month (March 2026) that has no leading days, so this went undetected.

Fix:

// Use the cell's actual date instead of the grid's month name / year
const cellMonthName = localizer.format(cell.date, "MMM", undefined);
const cellYear = cell.date.getFullYear();
const ariaLabel = `${cellMonthName} ${cell.date.getDate()}, ${cellYear}, ${
    cell.eventCount === 0
        ? "no events"
        : cell.eventCount === 1
          ? "1 event"
          : `${cell.eventCount} events`
}`;

Also add a test that catches this — the existing "sets an aria-label" test uses a month with no leading days:

it("uses the correct month name and year for a leading day cell", () => {
    // April 2026 starts on Wednesday → 3 leading days from March
    render(<MonthMiniGrid year={2026} month={3} events={[]} onDayClick={jest.fn()} localizer={localizer} />);
    // March 30 and 31 should say "Mar", not "Apr"
    expect(screen.getByLabelText("Mar 31, 2026, no events")).toBeTruthy();
});

it("uses the correct year for a leading day in January", () => {
    // January 2026: leading days are from December 2025
    render(<MonthMiniGrid year={2026} month={0} events={[]} onDayClick={jest.fn()} localizer={localizer} />);
    // Jan 2026 starts on Thursday → 4 leading days from Dec 2025
    expect(screen.getByLabelText("Dec 31, 2025, no events")).toBeTruthy();
});

⚠️ Low — Array index used as key for day cells

File: packages/pluggableWidgets/calendar-web/src/components/MonthMiniGrid.tsxdayCells.map((cell, index) => <div key={index} ...)

Note: The dayCells array is rebuilt from scratch on every render (not a stable list), so index keys won't cause mismatched reconciliation in practice. But using the actual date as key is both more correct and makes the intent clearer. Replace key={index} with key={cell.date.toISOString()} for day cells. Weekday headers (key={index} on a fixed 7-element array) are fine as-is.


⚠️ Low — cursor: pointer on non-interactive cells

File: packages/pluggableWidgets/calendar-web/src/ui/Calendar.scss.year-day-cell { cursor: pointer; }

Note: When onDayClick is absent, cells render without role="button" or tab stop, but the pointer cursor is still applied via CSS — creating a misleading affordance. Consider scoping it: either add a modifier class year-day-cell-interactive that carries the cursor style, or use .year-day-cell[role="button"] { cursor: pointer; }.


Positives

  • Year-boundary event clamping (clampedStart/clampedEnd against yearStart/yearEnd) is correctly implemented and thoroughly tested — the Dec→Jan and Nov→Feb(next year) boundary cases are explicitly covered.
  • Removing the phantom Day-view registration (|| yearEnabled) in favour of constraining drill-down to already-enabled views is a clean design improvement that avoids surprising authors who never configured a Day view.
  • getEnabledViewNames() as a single source of truth for enabled views — used by both buildVisibleViews() and resolveDayClickView() — is good architecture that removes the previous duplication.
  • YearViewController refactored from a static-only class to a plain const object, correctly resolving the no-extraneous-class ESLint finding without any API change.
  • console.warn when the configured day-click target is not an enabled view gives authors actionable runtime feedback instead of silently doing nothing.
  • Test file structure is solid: describe/it blocks, fake timers for today assertions, MonthMiniGrid stub in YearView tests, and explicit year-boundary edge cases in grouping tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants