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:
- 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.
- Lost defaults. Passing any formatters prop replaced the default formatMonthDropdown, so the month dropdown switched from short names to long ones.
- 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.
// 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.