From 8288d65177790689ec18707f13dc7870e1b37357 Mon Sep 17 00:00:00 2001 From: balajis-qb Date: Mon, 14 Jul 2025 11:44:04 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20customizable=20range?= =?UTF-8?q?=20separator=20for=20date=20range=20selection=20in=20DatePicker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced `rangeSeparator` prop to allow custom separators in date range formatting. - Updated `safeDateRangeFormat` to utilize the custom separator. - Added example for date range selection with a custom separator in documentation. - Enhanced tests to verify functionality of the new `rangeSeparator` feature. Closes #4741 --- docs-site/src/components/Examples/index.jsx | 5 +++++ .../src/examples/customRangeSeparator.js | 21 ++++++++++++++++++ docs/index.md | 1 + src/date_utils.ts | 9 ++++++-- src/index.tsx | 14 +++++++++++- src/test/date_utils_test.test.ts | 22 +++++++++++++++++++ src/test/datepicker_test.test.tsx | 17 ++++++++++++++ 7 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 docs-site/src/examples/customRangeSeparator.js diff --git a/docs-site/src/components/Examples/index.jsx b/docs-site/src/components/Examples/index.jsx index 0584f09c3a..12a231a1fc 100644 --- a/docs-site/src/components/Examples/index.jsx +++ b/docs-site/src/components/Examples/index.jsx @@ -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"; @@ -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, diff --git a/docs-site/src/examples/customRangeSeparator.js b/docs-site/src/examples/customRangeSeparator.js new file mode 100644 index 0000000000..808095e2a8 --- /dev/null +++ b/docs-site/src/examples/customRangeSeparator.js @@ -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 ( + + ); +}; diff --git a/docs/index.md b/docs/index.md index 098133f913..12558d304c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -92,3 +92,4 @@ | `value` | | | | | `withPortal` | | `false` | | | `yearItemNumber` | | `12` | | +| `rangeSeparator` | | `" - "` | | diff --git a/src/date_utils.ts b/src/date_utils.ts index 01a12fc05a..5f5e92f0b5 100644 --- a/src/date_utils.ts +++ b/src/date_utils.ts @@ -239,7 +239,11 @@ 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 ""; @@ -247,8 +251,9 @@ export function safeDateRangeFormat( 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}`; } /** diff --git a/src/index.tsx b/src/index.tsx index f07367d277..920070acde 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -191,6 +191,7 @@ export type DatePickerProps = OmitUnion< ariaInvalid?: string; ariaLabelledBy?: string; ariaRequired?: string; + rangeSeparator?: string; onChangeRaw?: ( event?: React.MouseEvent | React.KeyboardEvent, ) => void; @@ -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, @@ -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 ?? "", @@ -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 @@ -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 ?? [], { diff --git a/src/test/date_utils_test.test.ts b/src/test/date_utils_test.test.ts index b0dc9e71c0..0d53ad9c80 100644 --- a/src/test/date_utils_test.test.ts +++ b/src/test/date_utils_test.test.ts @@ -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", () => { diff --git a/src/test/datepicker_test.test.tsx b/src/test/datepicker_test.test.tsx index e9a76330c4..77f1998807 100644 --- a/src/test/datepicker_test.test.tsx +++ b/src/test/datepicker_test.test.tsx @@ -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( + , + ); + + const input = safeQuerySelector(container, "input"); + expect(input.value).toBe("2025/01/01 to 2025/01/03"); + }); }); describe("duplicate dates when multiple months", () => {