You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
🔶 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.tsx — ariaLabel 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 / yearconstcellMonthName=localizer.format(cell.date,"MMM",undefined);constcellYear=cell.date.getFullYear();constariaLabel=`${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 Marchrender(<MonthMiniGridyear={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 2025render(<MonthMiniGridyear={2026}month={0}events={[]}onDayClick={jest.fn()}localizer={localizer}/>);// Jan 2026 starts on Thursday → 4 leading days from Dec 2025expect(screen.getByLabelText("Dec 31, 2025, no events")).toBeTruthy();});
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
.css/.scss/.pngmodule types (typings/modules.d.ts) to satisfytsc— matches the sibling-widget convention.calendar-web/openspec/), adopting the package-scoped OpenSpec model.