All work

Open-source contribution · PR submitted for review

coss ui — Calendar customization and formatter compatibility

fix(calendar): merge consumer classNames and formatters with defaults

An independent contribution, submitted for review. It does not imply endorsement by, or affiliation with, Cal.com.

What was wrong

coss ui’s Calendar wraps DayPicker with its own default classes and a short month formatter. Three consumer customisations didn’t behave the way the component’s API suggests:

  1. Dropped classes. classNames was built from coss’s own keys only, so a class for any part coss doesn’t style — root, weekdays, selected, month_grid — disappeared without a warning.
  2. Lost defaults. Passing any formatters prop replaced the default formatMonthDropdown, so the month dropdown switched from short names to long ones.
  3. Undefined values. A formatter explicitly set to undefined broke the Calendar instead of falling back to the default.

Investigation

I reproduced each case against main before changing anything, then traced it to how the component combined its defaults with the consumer’s props before handing them to DayPicker. Because coss distributes components as source (a registry consumers copy from), the fix had to stay small, readable and follow the file’s existing TypeScript conventions.

The fix

Start the classNames merge from the consumer’s classes instead of from coss’s, and spread consumer formatters over the default rather than replacing the object. A key set to undefined now counts as not provided, so it can never clear a default.

packages/ui/src/components/calendar.tsx — excerpt from the PR
// A key set to `undefined` counts as not provided, so it never clears a default.
function withoutUndefined<T extends object>(value: T | undefined): Partial<T> {
  return Object.fromEntries(
    Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined),
  ) as Partial<T>;
}

formatters: {
  formatMonthDropdown: (month: Date, dateLib: DateLib) =>
    dateLib.format(month, "LLL"),
  ...withoutUndefined<CalendarFormatters>(userFormatters),
} as CalendarFormatters,

Backward compatibility

Parts coss already styles merge exactly as before, a conflicting utility still overrides the default one, and consumers who pass nothing see no change. Consumers can still replace the month formatter on purpose.

Regression tests

Eight new tests in packages/ui/test/components/calendar.test.tsx. Four of them fail on main and pass on the branch; the full suite runs 49 passing, 0 failing.

classNames
keeps classes for parts that have no coss default
classNames
merges custom classes with the coss defaults of styled parts
classNames
lets a conflicting utility override the default one
classNames
uses the defaults when classNames is undefined, empty, or has undefined values
classNames
keeps custom classes in range mode
formatters
keeps the short month dropdown labels when another formatter is passed
formatters
lets the consumer replace the default month dropdown formatter
formatters
uses the default when formatters is undefined or a formatter is undefined

Written for maintainers

The PR description sets out the reproduction, the cause, what changes and what deliberately doesn’t, and the exact files touched — both calendar.tsx copies synced with ui:sync and the rebuilt registry entry — while leaving out unrelated files the registry build rewrote. It’s open and awaiting maintainer review.