Skip to content

Commit 33b0fa2

Browse files
committed
feat: add renderCustomDayName prop for custom weekday header rendering
Add ability to customize day name rendering in calendar header similar to renderCustomHeader. Includes customDayNameCount parameter to support different styling when displaying multiple months. - Add ReactDatePickerCustomDayNameProps interface - Extend Calendar header() method with customDayNameCount parameter - Add comprehensive test coverage (7 test cases) - Include example in docs-site demonstrating usage
1 parent b198c49 commit 33b0fa2

7 files changed

Lines changed: 314 additions & 8 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ es
9898
tmp
9999

100100
.vscode
101+
.history
101102
*.iml
102103
.idea
103104

docs-site/src/components/Examples/config.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ConfigureFloatingUI from "../../examples/ts/configureFloatingUI?raw";
1717
import CustomInput from "../../examples/ts/customInput?raw";
1818
import RenderCustomHeader from "../../examples/ts/renderCustomHeader?raw";
1919
import RenderCustomHeaderTwoMonths from "../../examples/ts/renderCustomHeaderTwoMonths?raw";
20+
import RenderCustomDayName from "../../examples/ts/renderCustomDayName?raw";
2021
import RenderCustomDay from "../../examples/ts/renderCustomDay?raw";
2122
import RenderCustomMonth from "../../examples/ts/renderCustomMonth?raw";
2223
import RenderCustomQuarter from "../../examples/ts/renderCustomQuarter?raw";
@@ -184,6 +185,10 @@ export const EXAMPLE_CONFIG: IExampleConfig[] = [
184185
title: "Custom header with two months displayed",
185186
component: RenderCustomHeaderTwoMonths,
186187
},
188+
{
189+
title: "Custom Day Names",
190+
component: RenderCustomDayName,
191+
},
187192
{
188193
title: "Custom Day",
189194
component: RenderCustomDay,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const RenderCustomDayName = () => {
2+
const [selectedDate, setSelectedDate] = useState<Date | null>(new Date());
3+
4+
const renderDayName = ({
5+
day,
6+
shortName,
7+
fullName,
8+
customDayNameCount,
9+
}: ReactDatePickerCustomDayNameProps): React.ReactNode => {
10+
// Example: Add emoji or custom styling to day names
11+
const dayEmojis: { [key: string]: string } = {
12+
Monday: "🌙",
13+
Tuesday: "🔥",
14+
Wednesday: "🌊",
15+
Thursday: "⚡",
16+
Friday: "🎉",
17+
Saturday: "🌞",
18+
Sunday: "☀️",
19+
};
20+
21+
const emoji = dayEmojis[fullName] || "";
22+
23+
// Apply different styling based on customDayNameCount when showing multiple months
24+
const style = customDayNameCount > 0 ? { color: "red" } : {};
25+
26+
return (
27+
<>
28+
<span className="react-datepicker__sr-only">{fullName}</span>
29+
<span aria-hidden="true" title={fullName} style={style}>
30+
{emoji}
31+
<br />
32+
{shortName}
33+
</span>
34+
</>
35+
);
36+
};
37+
38+
return (
39+
<DatePicker
40+
selected={selectedDate}
41+
onChange={setSelectedDate}
42+
renderCustomDayName={renderDayName}
43+
monthsShown={2}
44+
/>
45+
);
46+
};
47+
48+
render(RenderCustomDayName);

src/calendar.tsx

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ export interface ReactDatePickerCustomHeaderProps {
114114
};
115115
}
116116

117+
export interface ReactDatePickerCustomDayNameProps {
118+
day: Date;
119+
shortName: string;
120+
fullName: string;
121+
locale?: Locale;
122+
customDayNameCount: number;
123+
}
124+
117125
type CalendarProps = React.PropsWithChildren<
118126
Omit<
119127
YearDropdownProps,
@@ -195,6 +203,9 @@ type CalendarProps = React.PropsWithChildren<
195203
renderCustomHeader?: (
196204
props: ReactDatePickerCustomHeaderProps,
197205
) => React.ReactElement;
206+
renderCustomDayName?: (
207+
props: ReactDatePickerCustomDayNameProps,
208+
) => React.ReactNode;
198209
onYearMouseEnter?: YearProps["onYearMouseEnter"];
199210
onYearMouseLeave?: YearProps["onYearMouseLeave"];
200211
monthAriaLabelPrefix?: MonthProps["ariaLabelPrefix"];
@@ -477,7 +488,10 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
477488
);
478489
};
479490

480-
header = (date: Date = this.state.date): React.ReactElement[] => {
491+
header = (
492+
date: Date = this.state.date,
493+
customDayNameCount: number = 0,
494+
): React.ReactElement[] => {
481495
// Return empty array if date is invalid
482496
if (!isValid(date)) {
483497
return [];
@@ -507,11 +521,38 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
507521
[0, 1, 2, 3, 4, 5, 6].map((offset) => {
508522
const day = addDays(startOfWeek, offset);
509523
const weekDayName = this.formatWeekday(day, this.props.locale);
524+
const fullDayName = formatDate(day, "EEEE", this.props.locale);
510525

511526
const weekDayClassName = this.props.weekDayClassName
512527
? this.props.weekDayClassName(day)
513528
: undefined;
514529

530+
// Use custom render if provided
531+
if (this.props.renderCustomDayName) {
532+
const customContent = this.props.renderCustomDayName({
533+
day,
534+
shortName: weekDayName,
535+
fullName: fullDayName,
536+
locale: this.props.locale,
537+
customDayNameCount,
538+
});
539+
540+
return (
541+
<div
542+
key={offset}
543+
role="columnheader"
544+
className={clsx(
545+
"react-datepicker__day-name",
546+
weekDayClassName,
547+
disabled ? "react-datepicker__day-name--disabled" : "",
548+
)}
549+
>
550+
{customContent}
551+
</div>
552+
);
553+
}
554+
555+
// Default render
515556
return (
516557
<div
517558
key={offset}
@@ -522,9 +563,7 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
522563
disabled ? "react-datepicker__day-name--disabled" : "",
523564
)}
524565
>
525-
<span className="react-datepicker__sr-only">
526-
{formatDate(day, "EEEE", this.props.locale)}
527-
</span>
566+
<span className="react-datepicker__sr-only">{fullDayName}</span>
528567
<span aria-hidden="true">{weekDayName}</span>
529568
</div>
530569
);
@@ -871,9 +910,9 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
871910
);
872911
};
873912

874-
renderDayNamesHeader = (monthDate: Date) => (
913+
renderDayNamesHeader = (monthDate: Date, customDayNameCount: number = 0) => (
875914
<div className="react-datepicker__day-names" role="row">
876-
{this.header(monthDate)}
915+
{this.header(monthDate, customDayNameCount)}
877916
</div>
878917
);
879918

@@ -1055,7 +1094,7 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
10551094
selectingDate={this.state.selectingDate}
10561095
monthShowsDuplicateDaysEnd={monthShowsDuplicateDaysEnd}
10571096
monthShowsDuplicateDaysStart={monthShowsDuplicateDaysStart}
1058-
dayNamesHeader={this.renderDayNamesHeader(monthDate)}
1097+
dayNamesHeader={this.renderDayNamesHeader(monthDate, i)}
10591098
/>
10601099
</div>,
10611100
);

src/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ export { default as CalendarContainer } from "./calendar_container";
6464

6565
export { registerLocale, setDefaultLocale, getDefaultLocale };
6666

67-
export { ReactDatePickerCustomHeaderProps } from "./calendar";
67+
export {
68+
ReactDatePickerCustomHeaderProps,
69+
ReactDatePickerCustomDayNameProps,
70+
} from "./calendar";
6871

6972
// Compares dates year+month combinations
7073
function hasPreSelectionChanged(

src/test/calendar_test.test.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,4 +2864,53 @@ describe("Calendar", () => {
28642864
);
28652865
});
28662866
});
2867+
2868+
describe("handleMonthChange with adjustDateOnChange but without setOpen", () => {
2869+
it("should call onSelect when adjustDateOnChange is true but setOpen is not provided", () => {
2870+
const onSelect = jest.fn();
2871+
const setPreSelection = jest.fn();
2872+
2873+
const { instance } = getCalendar({
2874+
adjustDateOnChange: true,
2875+
onSelect,
2876+
setPreSelection,
2877+
// setOpen is intentionally NOT provided to cover line 442
2878+
selected: new Date("2024-01-15T00:00:00"),
2879+
});
2880+
2881+
const targetMonth = new Date("2024-02-01T00:00:00");
2882+
act(() => {
2883+
instance?.handleMonthChange(targetMonth);
2884+
});
2885+
2886+
expect(onSelect).toHaveBeenCalled();
2887+
expect(setPreSelection).toHaveBeenCalled();
2888+
});
2889+
});
2890+
2891+
describe("header method with invalid date", () => {
2892+
it("should return empty array when date is invalid", () => {
2893+
const { instance } = getCalendar({
2894+
selected: new Date("2024-01-15T00:00:00"),
2895+
});
2896+
2897+
// Call header method with invalid date
2898+
const result = instance?.header(new Date("invalid"));
2899+
2900+
expect(result).toEqual([]);
2901+
});
2902+
2903+
it("should use default date parameter when not provided", () => {
2904+
const { instance } = getCalendar({
2905+
selected: new Date("2024-01-15T00:00:00"),
2906+
});
2907+
2908+
// Call header method without arguments to cover default parameter
2909+
const result = instance?.header();
2910+
2911+
expect(result).toBeDefined();
2912+
expect(Array.isArray(result)).toBe(true);
2913+
expect(result?.length).toBeGreaterThan(0);
2914+
});
2915+
});
28672916
});

0 commit comments

Comments
 (0)