Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clean-masks-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-day-picker": patch
---

fix: apply inline styles to all component slots.
94 changes: 94 additions & 0 deletions packages/react-day-picker/src/DayPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { MonthsProps } from "./components/Months";
import { DayPicker } from "./DayPicker";
import { labelMonthDropdown, labelYearDropdown } from "./labels/index.js";
import { ja } from "./locale/ja.js";
import { UI } from "./UI";

const testId = "test";
const dayPicker = () => screen.getByTestId(testId);
Expand Down Expand Up @@ -49,6 +50,99 @@ test("apply classnames and style according to props", () => {
expect(dayPicker()).toHaveStyle({ color: "rgb(255, 0, 0)" });
});

describe("when rendering custom inline styles for component slots", () => {
beforeEach(() => {
render(
<DayPicker
data-testid={testId}
styles={{
[UI.CaptionLabel]: { color: "purple" },
[UI.Chevron]: { fill: "red" },
[UI.NextMonthButton]: { backgroundColor: "green" },
[UI.PreviousMonthButton]: { backgroundColor: "blue" },
}}
/>,
);
});

test("applies the previous button style", () => {
expect(previousButton()).toHaveAttribute(
"style",
"background-color: blue;",
);
});

test("applies the next button style", () => {
expect(nextButton()).toHaveAttribute("style", "background-color: green;");
});

test("applies the chevron style", () => {
expect(document.querySelector(".rdp-chevron")).toHaveAttribute(
"style",
"fill: red;",
);
});

test("applies the caption label style", () => {
expect(document.querySelector(".rdp-caption_label")).toHaveAttribute(
"style",
"color: purple;",
);
});
});

describe("when rendering custom inline styles for dropdown slots", () => {
beforeEach(() => {
render(
<DayPicker
captionLayout="dropdown"
endMonth={new Date(2025, 11)}
month={new Date(2024, 0)}
startMonth={new Date(2024, 0)}
styles={{
[UI.CaptionLabel]: { color: "purple" },
[UI.Chevron]: { fill: "red" },
[UI.Dropdown]: { backgroundColor: "yellow" },
[UI.DropdownRoot]: { borderColor: "blue" },
[UI.MonthsDropdown]: { color: "green" },
[UI.YearsDropdown]: { fontWeight: 700 },
}}
/>,
);
});

test("applies the dropdown root style", () => {
expect(
screen.getByRole("combobox", { name: labelMonthDropdown() })
.parentElement,
).toHaveAttribute("style", "border-color: blue;");
});

test("applies the month dropdown style with the base dropdown style", () => {
expect(
screen.getByRole("combobox", { name: labelMonthDropdown() }),
).toHaveAttribute("style", "background-color: yellow; color: green;");
});

test("applies the year dropdown style with the base dropdown style", () => {
expect(
screen.getByRole("combobox", { name: labelYearDropdown() }),
).toHaveAttribute("style", "background-color: yellow; font-weight: 700;");
});

test("applies the dropdown caption label style", () => {
expect(
document.querySelector(".rdp-dropdown_root .rdp-caption_label"),
).toHaveAttribute("style", "color: purple;");
});

test("applies the dropdown chevron style", () => {
expect(
document.querySelector(".rdp-dropdown_root .rdp-chevron"),
).toHaveAttribute("style", "fill: red;");
});
});

test("forward aria attributes to the root element", () => {
render(
<DayPicker
Expand Down
23 changes: 21 additions & 2 deletions packages/react-day-picker/src/DayPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,20 @@ export function DayPicker(initialProps: DayPickerProps) {

const dataAttributes = getDataAttributes(props);

const getDropdownStyle = (dropdown: UI.MonthsDropdown | UI.YearsDropdown) => {
const dropdownStyle = styles?.[UI.Dropdown];
const specificDropdownStyle = styles?.[dropdown];

if (!dropdownStyle && !specificDropdownStyle) {
return undefined;
}

return {
...dropdownStyle,
...specificDropdownStyle,
};
};

const rootElRef = useRef<HTMLDivElement>(null);
useAnimation(rootElRef, Boolean(props.animate), {
classNames,
Expand Down Expand Up @@ -438,6 +452,7 @@ export function DayPicker(initialProps: DayPickerProps) {
<components.PreviousMonthButton
type="button"
className={classNames[UI.PreviousMonthButton]}
style={styles?.[UI.PreviousMonthButton]}
tabIndex={previousMonth ? undefined : -1}
aria-disabled={previousMonth ? undefined : true}
aria-label={labelPrevious(previousMonth)}
Expand All @@ -447,6 +462,7 @@ export function DayPicker(initialProps: DayPickerProps) {
<components.Chevron
disabled={previousMonth ? undefined : true}
className={classNames[UI.Chevron]}
style={styles?.[UI.Chevron]}
orientation={props.dir === "rtl" ? "right" : "left"}
/>
</components.PreviousMonthButton>
Expand Down Expand Up @@ -483,7 +499,7 @@ export function DayPicker(initialProps: DayPickerProps) {
formatters,
dateLib,
)}
style={styles?.[UI.Dropdown]}
style={getDropdownStyle(UI.MonthsDropdown)}
value={dateLib.getMonth(calendarMonth.date)}
/>
) : (
Expand Down Expand Up @@ -511,7 +527,7 @@ export function DayPicker(initialProps: DayPickerProps) {
dateLib,
Boolean(props.reverseYears),
)}
style={styles?.[UI.Dropdown]}
style={getDropdownStyle(UI.YearsDropdown)}
value={dateLib.getYear(calendarMonth.date)}
/>
) : (
Expand Down Expand Up @@ -553,6 +569,7 @@ export function DayPicker(initialProps: DayPickerProps) {
) : (
<components.CaptionLabel
className={classNames[UI.CaptionLabel]}
style={styles?.[UI.CaptionLabel]}
role="status"
aria-live="polite"
>
Expand All @@ -570,6 +587,7 @@ export function DayPicker(initialProps: DayPickerProps) {
<components.NextMonthButton
type="button"
className={classNames[UI.NextMonthButton]}
style={styles?.[UI.NextMonthButton]}
tabIndex={nextMonth ? undefined : -1}
aria-disabled={nextMonth ? undefined : true}
aria-label={labelNext(nextMonth)}
Expand All @@ -579,6 +597,7 @@ export function DayPicker(initialProps: DayPickerProps) {
<components.Chevron
disabled={nextMonth ? undefined : true}
className={classNames[UI.Chevron]}
style={styles?.[UI.Chevron]}
orientation={props.dir === "rtl" ? "left" : "right"}
/>
</components.NextMonthButton>
Expand Down
11 changes: 9 additions & 2 deletions packages/react-day-picker/src/components/Chevron.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React from "react";
*/
export function Chevron(props: {
className?: string;
style?: React.CSSProperties;
/**
* The size of the chevron.
*
Expand All @@ -19,11 +20,17 @@ export function Chevron(props: {
/** The orientation of the chevron. */
orientation?: "up" | "down" | "left" | "right";
}) {
const { size = 24, orientation = "left", className } = props;
const { size = 24, orientation = "left", className, style } = props;

return (
// biome-ignore lint/a11y/noSvgWithoutTitle: handled by the parent component
<svg className={className} width={size} height={size} viewBox="0 0 24 24">
<svg
className={className}
style={style}
width={size}
height={size}
viewBox="0 0 24 24"
>
{orientation === "up" && (
<polygon points="6.77 17 12.5 11.43 18.24 17 20 15.28 12.5 8 5 15.28" />
)}
Expand Down
10 changes: 8 additions & 2 deletions packages/react-day-picker/src/components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function Dropdown(
} & Omit<SelectHTMLAttributes<HTMLSelectElement>, "children">,
) {
const { options, className, ...selectProps } = props;
const { classNames, components } = useDayPicker();
const { classNames, components, styles } = useDayPicker();

const cssClassSelect = [classNames[UI.Dropdown], className].join(" ");

Expand All @@ -36,6 +36,7 @@ export function Dropdown(
<span
data-disabled={selectProps.disabled}
className={classNames[UI.DropdownRoot]}
style={styles?.[UI.DropdownRoot]}
>
<components.Select className={cssClassSelect} {...selectProps}>
{options?.map(({ value, label, disabled }) => (
Expand All @@ -44,12 +45,17 @@ export function Dropdown(
</components.Option>
))}
</components.Select>
<span className={classNames[UI.CaptionLabel]} aria-hidden>
<span
className={classNames[UI.CaptionLabel]}
style={styles?.[UI.CaptionLabel]}
aria-hidden
>
{selectedOption?.label}
<components.Chevron
orientation="down"
size={18}
className={classNames[UI.Chevron]}
style={styles?.[UI.Chevron]}
/>
</span>
</span>
Expand Down
5 changes: 5 additions & 0 deletions packages/react-day-picker/src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function Nav(
const {
components,
classNames,
styles,
labels: { labelPrevious, labelNext },
} = useDayPicker();

Expand All @@ -62,6 +63,7 @@ export function Nav(
<components.PreviousMonthButton
type="button"
className={classNames[UI.PreviousMonthButton]}
style={styles?.[UI.PreviousMonthButton]}
tabIndex={previousMonth ? undefined : -1}
aria-disabled={previousMonth ? undefined : true}
aria-label={labelPrevious(previousMonth)}
Expand All @@ -70,12 +72,14 @@ export function Nav(
<components.Chevron
disabled={previousMonth ? undefined : true}
className={classNames[UI.Chevron]}
style={styles?.[UI.Chevron]}
orientation="left"
/>
</components.PreviousMonthButton>
<components.NextMonthButton
type="button"
className={classNames[UI.NextMonthButton]}
style={styles?.[UI.NextMonthButton]}
tabIndex={nextMonth ? undefined : -1}
aria-disabled={nextMonth ? undefined : true}
aria-label={labelNext(nextMonth)}
Expand All @@ -85,6 +89,7 @@ export function Nav(
disabled={nextMonth ? undefined : true}
orientation="right"
className={classNames[UI.Chevron]}
style={styles?.[UI.Chevron]}
/>
</components.NextMonthButton>
</nav>
Expand Down
Loading