Skip to content

feat: ✨ Add customizable range separator for date range selection in DatePicker #5737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 docs-site/src/components/Examples/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import CustomTimeInput from "../../examples/customTimeInput?raw";
import CloseOnScroll from "../../examples/closeOnScroll?raw";
import CloseOnScrollCallback from "../../examples/closeOnScrollCallback?raw";
import SelectsRange from "../../examples/selectsRange?raw";
import SelectsRangeWithCustomSeparator from "../../examples/customRangeSeparator?raw";
import selectsRangeWithDisabledDates from "../../examples/selectsRangeWithDisabledDates?raw";
import CalendarStartDay from "../../examples/calendarStartDay?raw";
import ExternalForm from "../../examples/externalForm?raw";
Expand Down Expand Up @@ -242,6 +243,10 @@ export default class exampleComponents extends React.Component {
title: "Date range for one datepicker",
component: SelectsRange,
},
{
title: "Date range for one datepicker with custom range separator",
component: SelectsRangeWithCustomSeparator,
},
{
title: "Date range for one datepicker with disabled dates highlighted",
component: selectsRangeWithDisabledDates,
Expand Down
21 changes: 21 additions & 0 deletions docs-site/src/examples/customRangeSeparator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
() => {
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(addDays(new Date(), 3));

const onChange = (dates) => {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
};

return (
<DatePicker
selected={startDate}
onChange={onChange}
startDate={startDate}
endDate={endDate}
selectsRange
rangeSeparator=" to "
/>
);
};
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@
| `value` | | | |
| `withPortal` | | `false` | |
| `yearItemNumber` | | `12` | |
| `rangeSeparator` | | `" - "` | |
9 changes: 7 additions & 2 deletions src/date_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,21 @@ export const DATE_RANGE_SEPARATOR = " - ";
export function safeDateRangeFormat(
startDate: Date | null | undefined,
endDate: Date | null | undefined,
props: { dateFormat: string | string[]; locale?: Locale },
props: {
dateFormat: string | string[];
locale?: Locale;
rangeSeparator?: string;
},
): string {
if (!startDate) {
return "";
}

const formattedStartDate = safeDateFormat(startDate, props);
const formattedEndDate = endDate ? safeDateFormat(endDate, props) : "";
const dateRangeSeparator = props.rangeSeparator || DATE_RANGE_SEPARATOR;

return `${formattedStartDate}${DATE_RANGE_SEPARATOR}${formattedEndDate}`;
return `${formattedStartDate}${dateRangeSeparator}${formattedEndDate}`;
}

/**
Expand Down
14 changes: 13 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export type DatePickerProps = OmitUnion<
ariaInvalid?: string;
ariaLabelledBy?: string;
ariaRequired?: string;
rangeSeparator?: string;
onChangeRaw?: (
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
) => void;
Expand Down Expand Up @@ -265,6 +266,7 @@ export default class DatePicker extends Component<
monthsShown: 1,
outsideClickIgnoreClass: OUTSIDE_CLICK_IGNORE_CLASS,
readOnly: false,
rangeSeparator: DATE_RANGE_SEPARATOR,
withPortal: false,
selectsDisabledDaysInRange: false,
shouldCloseOnSelect: true,
Expand Down Expand Up @@ -615,8 +617,16 @@ export default class DatePicker extends Component<
event?.target instanceof HTMLInputElement ? event.target.value : "";

if (selectsRange) {
const rangeSeparator = this.props.rangeSeparator as string;
const trimmedRangeSeparator = rangeSeparator.trim();

const [valueStart, valueEnd] = value
.split(dateFormat.includes("-") ? DATE_RANGE_SEPARATOR : "-", 2)
.split(
dateFormat.includes(trimmedRangeSeparator)
? rangeSeparator
: trimmedRangeSeparator,
2,
)
.map((val) => val.trim());
const startDateNew = parseDate(
valueStart ?? "",
Expand Down Expand Up @@ -1340,6 +1350,7 @@ export default class DatePicker extends Component<
const customInputRef = this.props.customInputRef || "ref";
const { dateFormat = DatePicker.defaultProps.dateFormat, locale } =
this.props;

const inputValue =
typeof this.props.value === "string"
? this.props.value
Expand All @@ -1349,6 +1360,7 @@ export default class DatePicker extends Component<
? safeDateRangeFormat(this.props.startDate, this.props.endDate, {
dateFormat,
locale,
rangeSeparator: this.props.rangeSeparator,
})
: this.props.selectsMultiple
? safeMultipleDatesFormat(this.props.selectedDates ?? [], {
Expand Down
22 changes: 22 additions & 0 deletions src/test/date_utils_test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,28 @@ describe("date_utils", () => {
"04/20/2021 - 04/28/2021",
);
});

it("should return a formatted startDate followed by the provided rangeSeparator when endDate is null", () => {
const startDate = new Date("2021-04-20 00:00:00");
const endDate = undefined;
expect(
safeDateRangeFormat(startDate, endDate, {
...props,
rangeSeparator: " to ",
}),
).toBe("04/20/2021 to ");
});

it("should return a formatted startDate followed by the provided rangeSeparator followed by a formatted endDate when startDate and endDate both have values", () => {
const startDate = new Date("2021-04-20 00:00:00");
const endDate = new Date("2021-04-28 00:00:00");
expect(
safeDateRangeFormat(startDate, endDate, {
...props,
rangeSeparator: " to ",
}),
).toBe("04/20/2021 to 04/28/2021");
});
});

describe("getHolidaysMap", () => {
Expand Down
17 changes: 17 additions & 0 deletions src/test/datepicker_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3495,6 +3495,23 @@ describe("DatePicker", () => {

expect(onChangeSpy).not.toHaveBeenCalled();
});

it("should render custom separator when `rangeSeparator` is provided", () => {
const onChangeSpy = jest.fn();
const { container } = render(
<DatePicker
selectsRange
rangeSeparator=" to "
startDate={newDate("2025-01-01")}
endDate={newDate("2025-01-03")}
onChange={onChangeSpy}
dateFormat="yyyy/MM/dd"
/>,
);

const input = safeQuerySelector<HTMLInputElement>(container, "input");
expect(input.value).toBe("2025/01/01 to 2025/01/03");
});
});

describe("duplicate dates when multiple months", () => {
Expand Down
Loading