Skip to content

Commit 2d7195d

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 2d7195d

File tree

6 files changed

+265
-8
lines changed

6 files changed

+265
-8
lines changed

.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(
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { render } from "@testing-library/react";
2+
import React from "react";
3+
4+
import DatePicker from "../index";
5+
import { ReactDatePickerCustomDayNameProps } from "../calendar";
6+
7+
describe("renderCustomDayName", () => {
8+
it("should call renderCustomDayName function with correct parameters", () => {
9+
const renderCustomDayName = jest.fn(
10+
({ shortName }: ReactDatePickerCustomDayNameProps) => (
11+
<span>{shortName}</span>
12+
),
13+
);
14+
15+
render(<DatePicker renderCustomDayName={renderCustomDayName} inline />);
16+
17+
// Should be called 7 times (one for each day of the week)
18+
expect(renderCustomDayName).toHaveBeenCalledTimes(7);
19+
20+
// Check that it's called with correct parameters
21+
const firstCall = renderCustomDayName.mock.calls[0]?.[0];
22+
expect(firstCall).toBeDefined();
23+
expect(firstCall).toHaveProperty("day");
24+
expect(firstCall).toHaveProperty("shortName");
25+
expect(firstCall).toHaveProperty("fullName");
26+
expect(firstCall).toHaveProperty("locale");
27+
expect(firstCall).toHaveProperty("customDayNameCount");
28+
expect(firstCall?.day).toBeInstanceOf(Date);
29+
expect(typeof firstCall?.shortName).toBe("string");
30+
expect(typeof firstCall?.fullName).toBe("string");
31+
expect(typeof firstCall?.customDayNameCount).toBe("number");
32+
});
33+
34+
it("should render custom day names", () => {
35+
const renderCustomDayName = ({
36+
shortName,
37+
}: ReactDatePickerCustomDayNameProps) => (
38+
<span className="custom-day-name">Custom-{shortName}</span>
39+
);
40+
41+
const { container } = render(
42+
<DatePicker renderCustomDayName={renderCustomDayName} inline />,
43+
);
44+
45+
const customDayNames = container.querySelectorAll(".custom-day-name");
46+
expect(customDayNames).toHaveLength(7);
47+
expect(customDayNames[0]?.textContent).toContain("Custom-");
48+
});
49+
50+
it("should render default day names when renderCustomDayName is not provided", () => {
51+
const { container } = render(<DatePicker inline />);
52+
53+
const dayNames = container.querySelectorAll(".react-datepicker__day-name");
54+
expect(dayNames).toHaveLength(7);
55+
56+
// Check that default structure is present (sr-only + aria-hidden)
57+
const firstDayName = dayNames[0];
58+
expect(
59+
firstDayName?.querySelector(".react-datepicker__sr-only"),
60+
).not.toBeNull();
61+
expect(firstDayName?.querySelector('[aria-hidden="true"]')).not.toBeNull();
62+
});
63+
64+
it("should use custom day names with accessibility", () => {
65+
const renderCustomDayName = ({
66+
shortName,
67+
fullName,
68+
}: ReactDatePickerCustomDayNameProps) => (
69+
<>
70+
<span className="react-datepicker__sr-only">{fullName}</span>
71+
<span aria-hidden="true">{shortName}</span>
72+
</>
73+
);
74+
75+
const { container } = render(
76+
<DatePicker renderCustomDayName={renderCustomDayName} inline />,
77+
);
78+
79+
const dayNames = container.querySelectorAll(".react-datepicker__day-name");
80+
expect(dayNames).toHaveLength(7);
81+
82+
// Check that accessibility structure is maintained
83+
dayNames.forEach((dayName) => {
84+
expect(
85+
dayName.querySelector(".react-datepicker__sr-only"),
86+
).not.toBeNull();
87+
expect(dayName.querySelector('[aria-hidden="true"]')).not.toBeNull();
88+
});
89+
});
90+
91+
it("should apply weekDayClassName along with custom day names", () => {
92+
const weekDayClassName = (date: Date) => {
93+
return date.getDay() === 0 || date.getDay() === 6 ? "weekend" : "";
94+
};
95+
96+
const renderCustomDayName = ({
97+
shortName,
98+
}: ReactDatePickerCustomDayNameProps) => <span>{shortName}</span>;
99+
100+
const { container } = render(
101+
<DatePicker
102+
renderCustomDayName={renderCustomDayName}
103+
weekDayClassName={weekDayClassName}
104+
inline
105+
/>,
106+
);
107+
108+
const weekendDays = container.querySelectorAll(
109+
".react-datepicker__day-name.weekend",
110+
);
111+
// Should have 2 weekend days (Saturday and Sunday)
112+
expect(weekendDays.length).toBeGreaterThanOrEqual(2);
113+
});
114+
115+
it("should pass locale to renderCustomDayName", () => {
116+
const renderCustomDayName = jest.fn(
117+
({ shortName }: ReactDatePickerCustomDayNameProps) => (
118+
<span>{shortName}</span>
119+
),
120+
);
121+
122+
render(
123+
<DatePicker
124+
renderCustomDayName={renderCustomDayName}
125+
locale="en-US"
126+
inline
127+
/>,
128+
);
129+
130+
const firstCall = renderCustomDayName.mock.calls[0]?.[0];
131+
expect(firstCall?.locale).toBe("en-US");
132+
});
133+
134+
it("should pass customDayNameCount when displaying multiple months", () => {
135+
const renderCustomDayName = jest.fn(
136+
({ shortName }: ReactDatePickerCustomDayNameProps) => (
137+
<span>{shortName}</span>
138+
),
139+
);
140+
141+
render(
142+
<DatePicker
143+
renderCustomDayName={renderCustomDayName}
144+
monthsShown={3}
145+
inline
146+
/>,
147+
);
148+
149+
// Should be called 7 times per month, so 21 times for 3 months
150+
expect(renderCustomDayName).toHaveBeenCalledTimes(21);
151+
152+
// Check that customDayNameCount is different for each month
153+
const firstMonthCall = renderCustomDayName.mock.calls[0]?.[0];
154+
const secondMonthCall = renderCustomDayName.mock.calls[7]?.[0];
155+
const thirdMonthCall = renderCustomDayName.mock.calls[14]?.[0];
156+
157+
expect(firstMonthCall?.customDayNameCount).toBe(0);
158+
expect(secondMonthCall?.customDayNameCount).toBe(1);
159+
expect(thirdMonthCall?.customDayNameCount).toBe(2);
160+
});
161+
});

0 commit comments

Comments
 (0)