Skip to content

Commit 8288d65

Browse files
committed
feat: ✨ Add customizable range separator for date range selection in DatePicker
- 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
1 parent ec4ce2c commit 8288d65

File tree

7 files changed

+86
-3
lines changed

7 files changed

+86
-3
lines changed

docs-site/src/components/Examples/index.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ import CustomTimeInput from "../../examples/customTimeInput?raw";
100100
import CloseOnScroll from "../../examples/closeOnScroll?raw";
101101
import CloseOnScrollCallback from "../../examples/closeOnScrollCallback?raw";
102102
import SelectsRange from "../../examples/selectsRange?raw";
103+
import SelectsRangeWithCustomSeparator from "../../examples/customRangeSeparator?raw";
103104
import selectsRangeWithDisabledDates from "../../examples/selectsRangeWithDisabledDates?raw";
104105
import CalendarStartDay from "../../examples/calendarStartDay?raw";
105106
import ExternalForm from "../../examples/externalForm?raw";
@@ -242,6 +243,10 @@ export default class exampleComponents extends React.Component {
242243
title: "Date range for one datepicker",
243244
component: SelectsRange,
244245
},
246+
{
247+
title: "Date range for one datepicker with custom range separator",
248+
component: SelectsRangeWithCustomSeparator,
249+
},
245250
{
246251
title: "Date range for one datepicker with disabled dates highlighted",
247252
component: selectsRangeWithDisabledDates,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
() => {
2+
const [startDate, setStartDate] = useState(new Date());
3+
const [endDate, setEndDate] = useState(addDays(new Date(), 3));
4+
5+
const onChange = (dates) => {
6+
const [start, end] = dates;
7+
setStartDate(start);
8+
setEndDate(end);
9+
};
10+
11+
return (
12+
<DatePicker
13+
selected={startDate}
14+
onChange={onChange}
15+
startDate={startDate}
16+
endDate={endDate}
17+
selectsRange
18+
rangeSeparator=" to "
19+
/>
20+
);
21+
};

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,4 @@
9292
| `value` | | | |
9393
| `withPortal` | | `false` | |
9494
| `yearItemNumber` | | `12` | |
95+
| `rangeSeparator` | | `" - "` | |

src/date_utils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,16 +239,21 @@ export const DATE_RANGE_SEPARATOR = " - ";
239239
export function safeDateRangeFormat(
240240
startDate: Date | null | undefined,
241241
endDate: Date | null | undefined,
242-
props: { dateFormat: string | string[]; locale?: Locale },
242+
props: {
243+
dateFormat: string | string[];
244+
locale?: Locale;
245+
rangeSeparator?: string;
246+
},
243247
): string {
244248
if (!startDate) {
245249
return "";
246250
}
247251

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

251-
return `${formattedStartDate}${DATE_RANGE_SEPARATOR}${formattedEndDate}`;
256+
return `${formattedStartDate}${dateRangeSeparator}${formattedEndDate}`;
252257
}
253258

254259
/**

src/index.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export type DatePickerProps = OmitUnion<
191191
ariaInvalid?: string;
192192
ariaLabelledBy?: string;
193193
ariaRequired?: string;
194+
rangeSeparator?: string;
194195
onChangeRaw?: (
195196
event?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
196197
) => void;
@@ -265,6 +266,7 @@ export default class DatePicker extends Component<
265266
monthsShown: 1,
266267
outsideClickIgnoreClass: OUTSIDE_CLICK_IGNORE_CLASS,
267268
readOnly: false,
269+
rangeSeparator: DATE_RANGE_SEPARATOR,
268270
withPortal: false,
269271
selectsDisabledDaysInRange: false,
270272
shouldCloseOnSelect: true,
@@ -615,8 +617,16 @@ export default class DatePicker extends Component<
615617
event?.target instanceof HTMLInputElement ? event.target.value : "";
616618

617619
if (selectsRange) {
620+
const rangeSeparator = this.props.rangeSeparator as string;
621+
const trimmedRangeSeparator = rangeSeparator.trim();
622+
618623
const [valueStart, valueEnd] = value
619-
.split(dateFormat.includes("-") ? DATE_RANGE_SEPARATOR : "-", 2)
624+
.split(
625+
dateFormat.includes(trimmedRangeSeparator)
626+
? rangeSeparator
627+
: trimmedRangeSeparator,
628+
2,
629+
)
620630
.map((val) => val.trim());
621631
const startDateNew = parseDate(
622632
valueStart ?? "",
@@ -1340,6 +1350,7 @@ export default class DatePicker extends Component<
13401350
const customInputRef = this.props.customInputRef || "ref";
13411351
const { dateFormat = DatePicker.defaultProps.dateFormat, locale } =
13421352
this.props;
1353+
13431354
const inputValue =
13441355
typeof this.props.value === "string"
13451356
? this.props.value
@@ -1349,6 +1360,7 @@ export default class DatePicker extends Component<
13491360
? safeDateRangeFormat(this.props.startDate, this.props.endDate, {
13501361
dateFormat,
13511362
locale,
1363+
rangeSeparator: this.props.rangeSeparator,
13521364
})
13531365
: this.props.selectsMultiple
13541366
? safeMultipleDatesFormat(this.props.selectedDates ?? [], {

src/test/date_utils_test.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,28 @@ describe("date_utils", () => {
12711271
"04/20/2021 - 04/28/2021",
12721272
);
12731273
});
1274+
1275+
it("should return a formatted startDate followed by the provided rangeSeparator when endDate is null", () => {
1276+
const startDate = new Date("2021-04-20 00:00:00");
1277+
const endDate = undefined;
1278+
expect(
1279+
safeDateRangeFormat(startDate, endDate, {
1280+
...props,
1281+
rangeSeparator: " to ",
1282+
}),
1283+
).toBe("04/20/2021 to ");
1284+
});
1285+
1286+
it("should return a formatted startDate followed by the provided rangeSeparator followed by a formatted endDate when startDate and endDate both have values", () => {
1287+
const startDate = new Date("2021-04-20 00:00:00");
1288+
const endDate = new Date("2021-04-28 00:00:00");
1289+
expect(
1290+
safeDateRangeFormat(startDate, endDate, {
1291+
...props,
1292+
rangeSeparator: " to ",
1293+
}),
1294+
).toBe("04/20/2021 to 04/28/2021");
1295+
});
12741296
});
12751297

12761298
describe("getHolidaysMap", () => {

src/test/datepicker_test.test.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,6 +3495,23 @@ describe("DatePicker", () => {
34953495

34963496
expect(onChangeSpy).not.toHaveBeenCalled();
34973497
});
3498+
3499+
it("should render custom separator when `rangeSeparator` is provided", () => {
3500+
const onChangeSpy = jest.fn();
3501+
const { container } = render(
3502+
<DatePicker
3503+
selectsRange
3504+
rangeSeparator=" to "
3505+
startDate={newDate("2025-01-01")}
3506+
endDate={newDate("2025-01-03")}
3507+
onChange={onChangeSpy}
3508+
dateFormat="yyyy/MM/dd"
3509+
/>,
3510+
);
3511+
3512+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
3513+
expect(input.value).toBe("2025/01/01 to 2025/01/03");
3514+
});
34983515
});
34993516

35003517
describe("duplicate dates when multiple months", () => {

0 commit comments

Comments
 (0)