Skip to content

Commit 65897c1

Browse files
committed
fix(calendar-web): apply custom Header day format to column headers
In Custom view the Header day format was only wired into react-big-calendar's toolbar-title keys (dayHeaderFormat/monthHeaderFormat), so the day/week/month column headers still used RBC's default dayFormat (dd eee, rendering "07 Tue") and the user's pattern was silently dropped. Wire customViewHeaderDayFormat into RBC's per-column keys: dayFormat (day/week columns) and weekdayFormat (month weekday headers). RBC shares a single global dayFormat across day and week, so when both items set different patterns the week pattern wins (matching the existing timeGutterFormat precedence) and a warning is logged. Add unit tests covering both formats, the collision precedence, and the no-format regression.
1 parent 34eccd5 commit 65897c1

3 files changed

Lines changed: 108 additions & 0 deletions

File tree

packages/pluggableWidgets/calendar-web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
- We fixed an issue in Custom view where the "Header day format" was only applied to the toolbar title and not to the day, week, and month column headers.
12+
913
## [2.4.0] - 2026-03-20
1014

1115
### Fixed

packages/pluggableWidgets/calendar-web/src/__tests__/Calendar.spec.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,79 @@ describe("CalendarPropsBuilder validation", () => {
256256
expect(result.timeslots).toBe(2);
257257
});
258258
});
259+
260+
describe("CalendarPropsBuilder column header formats", () => {
261+
const buildFormats = (toolbarItems: CalendarContainerProps["toolbarItems"]): any => {
262+
const localizer = {
263+
format: jest.fn((_date: Date, pattern: string) => pattern),
264+
parse: jest.fn(),
265+
startOfWeek: jest.fn(),
266+
getDay: jest.fn(),
267+
messages: {}
268+
} as any;
269+
const builder = new CalendarPropsBuilder({ ...customViewProps, toolbarItems });
270+
return { formats: builder.build(localizer, "en").formats, localizer };
271+
};
272+
273+
const toolbarItem = (
274+
itemType: string,
275+
overrides: Partial<CalendarContainerProps["toolbarItems"][number]> = {}
276+
): CalendarContainerProps["toolbarItems"][number] =>
277+
({
278+
itemType,
279+
position: "left",
280+
renderMode: "button",
281+
buttonStyle: "default",
282+
...overrides
283+
}) as any;
284+
285+
it("wires 'Header day format' on a day item into RBC dayFormat (the column header)", () => {
286+
const { formats, localizer } = buildFormats([
287+
toolbarItem("day", { customViewHeaderDayFormat: dynamic("EE dd-MM") })
288+
]);
289+
290+
expect(typeof formats.dayFormat).toBe("function");
291+
formats.dayFormat(new Date("2025-04-28T12:00:00Z"), "en", localizer);
292+
expect(localizer.format).toHaveBeenCalledWith(expect.any(Date), "EE dd-MM", "en");
293+
});
294+
295+
it("wires 'Header day format' on a month item into RBC weekdayFormat", () => {
296+
const { formats, localizer } = buildFormats([
297+
toolbarItem("month", { customViewHeaderDayFormat: dynamic("EEEE") })
298+
]);
299+
300+
expect(typeof formats.weekdayFormat).toBe("function");
301+
formats.weekdayFormat(new Date("2025-04-28T12:00:00Z"), "en", localizer);
302+
expect(localizer.format).toHaveBeenCalledWith(expect.any(Date), "EEEE", "en");
303+
});
304+
305+
it("prefers the week pattern for the shared dayFormat when day and week both set it", () => {
306+
const warn = jest.spyOn(console, "warn").mockImplementation(() => undefined);
307+
const { formats, localizer } = buildFormats([
308+
toolbarItem("day", { customViewHeaderDayFormat: dynamic("dd") }),
309+
toolbarItem("week", { customViewHeaderDayFormat: dynamic("EE dd-MM") })
310+
]);
311+
312+
formats.dayFormat(new Date("2025-04-28T12:00:00Z"), "en", localizer);
313+
expect(localizer.format).toHaveBeenCalledWith(expect.any(Date), "EE dd-MM", "en");
314+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("shares a single"));
315+
warn.mockRestore();
316+
});
317+
318+
it("leaves dayFormat/weekdayFormat unset when no header format is configured (RBC defaults preserved)", () => {
319+
const { formats } = buildFormats([toolbarItem("day"), toolbarItem("month")]);
320+
321+
expect(formats.dayFormat).toBeUndefined();
322+
expect(formats.weekdayFormat).toBeUndefined();
323+
});
324+
325+
it("wires 'Time gutter format' on a day item into RBC timeGutterFormat", () => {
326+
const { formats, localizer } = buildFormats([
327+
toolbarItem("day", { customViewGutterTimeFormat: dynamic("HH:mm") })
328+
]);
329+
330+
expect(typeof formats.timeGutterFormat).toBe("function");
331+
formats.timeGutterFormat(new Date("2025-04-28T14:00:00Z"), "en", localizer);
332+
expect(localizer.format).toHaveBeenCalledWith(expect.any(Date), "HH:mm", "en");
333+
});
334+
});

packages/pluggableWidgets/calendar-web/src/helpers/CalendarPropsBuilder.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,34 @@ export class CalendarPropsBuilder {
225225
loc.format(date, monthHeaderPattern, culture);
226226
}
227227

228+
// Per-column headers — distinct from the toolbar title above.
229+
// RBC renders the "07 Tue" day-column headers via `dayFormat` (week/day time-grid,
230+
// TimeGridHeader.js) and the month weekday headers via `weekdayFormat` (Month.js).
231+
// These are separate from dayHeaderFormat/monthHeaderFormat (the toolbar title), so we
232+
// must set them explicitly or RBC's date-fns defaults ("dd eee") always win.
233+
if (monthHeaderPattern) {
234+
formats.weekdayFormat = (date: Date, culture: string, loc: DateLocalizer) =>
235+
loc.format(date, monthHeaderPattern, culture);
236+
}
237+
238+
// RBC exposes a single global `dayFormat` shared by the day AND week column headers, so
239+
// when a day item and a week item each carry a different "Header day format" we can only
240+
// honor one. Precedence matches `chosenTimeGutter` below (week → day → work_week) for
241+
// consistency across the shared-key formats. weekHeaderPattern already folds in
242+
// week/work_week; dayHeaderPattern is the "day" item.
243+
const columnDayPattern: string | undefined = weekHeaderPattern || dayHeaderPattern;
244+
if (weekHeaderPattern && dayHeaderPattern && weekHeaderPattern !== dayHeaderPattern) {
245+
console.warn(
246+
`[Calendar] Both week and day "Header day format" are set to different patterns ` +
247+
`("${weekHeaderPattern}" vs "${dayHeaderPattern}"). react-big-calendar shares a single ` +
248+
`column-header format, so "${columnDayPattern}" will be used for both views.`
249+
);
250+
}
251+
if (columnDayPattern) {
252+
formats.dayFormat = (date: Date, culture: string, loc: DateLocalizer) =>
253+
loc.format(date, columnDayPattern, culture);
254+
}
255+
228256
const agendaHeaderPattern = getPattern(byType.get("agenda")?.customViewHeaderDayFormat);
229257
if (agendaHeaderPattern) {
230258
formats.agendaHeaderFormat = (range: { start: Date; end: Date }, culture: string, loc: DateLocalizer) =>

0 commit comments

Comments
 (0)