Skip to content

Commit 4cad0b8

Browse files
committed
feat: customizable start/end labels for range time input
When `selectsRange` is combined with `showTimeInput`, the two time inputs had hardcoded "(Start)" / "(End)" suffixes appended to `timeInputLabel`, with no way to override them. Add `timeInputStartLabel` and `timeInputEndLabel` props. When provided, each replaces the corresponding caption entirely; otherwise the existing `"<timeInputLabel> (Start/End)"` behavior is unchanged. Single (non-range) mode is unaffected. Includes a docs-site example and tests covering default behaviour, per-side fallback, and precedence over `timeInputLabel`. Fixes #6280
1 parent 548a1f3 commit 4cad0b8

4 files changed

Lines changed: 159 additions & 2 deletions

File tree

docs-site/src/components/Examples/config.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import Inline from "../../examples/ts/inline?raw";
6262
import InlineDisabled from "../../examples/ts/disabledInline?raw";
6363
import InlineVisible from "../../examples/ts/inlineVisible?raw";
6464
import TimeInput from "../../examples/ts/timeInput?raw";
65+
import RangeTimeInputLabels from "../../examples/ts/rangeTimeInputLabels?raw";
6566
import Locale from "../../examples/ts/locale?raw";
6667
import LocaleWithTime from "../../examples/ts/localeWithTime?raw";
6768
import LocaleWithoutGlobalVariable from "../../examples/ts/localeWithoutGlobalVariable?raw";
@@ -370,6 +371,10 @@ export const EXAMPLE_CONFIG: IExampleConfig[] = [
370371
title: "Input Time",
371372
component: TimeInput,
372373
},
374+
{
375+
title: "Input Time with Range (custom start/end labels)",
376+
component: RangeTimeInputLabels,
377+
},
373378
{
374379
title: "Locale",
375380
component: Locale,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const RangeTimeInputLabels = () => {
2+
const [startDate, setStartDate] = useState<Date | null>(new Date());
3+
const [endDate, setEndDate] = useState<Date | null>(null);
4+
5+
const onChange = (dates: [Date | null, Date | null]) => {
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+
showTimeInput
19+
timeInputStartLabel="Start time"
20+
timeInputEndLabel="End time"
21+
dateFormat="MM/dd/yyyy h:mm aa"
22+
/>
23+
);
24+
};
25+
26+
render(RangeTimeInputLabels);

src/calendar.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ type CalendarProps = React.PropsWithChildren<
223223
onTimeChange?: (time: Date, modifyDateType?: "start" | "end") => void;
224224
timeFormat?: TimeProps["format"];
225225
timeIntervals?: TimeProps["intervals"];
226+
timeInputStartLabel?: string;
227+
timeInputEndLabel?: string;
226228
} & (
227229
| ({
228230
showMonthYearDropdown: true;
@@ -1212,7 +1214,10 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
12121214
onChange={(time: Date) => {
12131215
this.props.onTimeChange?.(time, "start");
12141216
}}
1215-
timeInputLabel={(this.props.timeInputLabel ?? "Time") + " (Start)"}
1217+
timeInputLabel={
1218+
this.props.timeInputStartLabel ??
1219+
(this.props.timeInputLabel ?? "Time") + " (Start)"
1220+
}
12161221
/>
12171222
<InputTime
12181223
{...Calendar.defaultProps}
@@ -1222,7 +1227,10 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
12221227
onChange={(time: Date) => {
12231228
this.props.onTimeChange?.(time, "end");
12241229
}}
1225-
timeInputLabel={(this.props.timeInputLabel ?? "Time") + " (End)"}
1230+
timeInputLabel={
1231+
this.props.timeInputEndLabel ??
1232+
(this.props.timeInputLabel ?? "Time") + " (End)"
1233+
}
12261234
/>
12271235
</>
12281236
);

src/test/datepicker_test.test.tsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6821,6 +6821,124 @@ describe("DatePicker", () => {
68216821
expect(timeInputs[0]?.value).toBe("");
68226822
expect(timeInputs[1]?.value).toBe("");
68236823
});
6824+
6825+
describe("timeInputStartLabel / timeInputEndLabel props (#6280)", () => {
6826+
const rangeStart = newDate("2024-01-15 09:00:00");
6827+
const rangeEnd = newDate("2024-01-20 14:30:00");
6828+
6829+
const captionsOf = (container: HTMLElement) =>
6830+
Array.from(
6831+
container.querySelectorAll(".react-datepicker-time__caption"),
6832+
).map((n) => n.textContent);
6833+
6834+
it("defaults to the hardcoded English suffixes", () => {
6835+
const { container } = render(
6836+
<DatePicker
6837+
selectsRange
6838+
startDate={rangeStart}
6839+
endDate={rangeEnd}
6840+
onChange={jest.fn()}
6841+
showTimeInput
6842+
inline
6843+
/>,
6844+
);
6845+
expect(captionsOf(container)).toEqual(["Time (Start)", "Time (End)"]);
6846+
});
6847+
6848+
it("appends the suffixes to a custom timeInputLabel (existing behavior)", () => {
6849+
const { container } = render(
6850+
<DatePicker
6851+
selectsRange
6852+
startDate={rangeStart}
6853+
endDate={rangeEnd}
6854+
onChange={jest.fn()}
6855+
showTimeInput
6856+
timeInputLabel="Uhrzeit"
6857+
inline
6858+
/>,
6859+
);
6860+
expect(captionsOf(container)).toEqual([
6861+
"Uhrzeit (Start)",
6862+
"Uhrzeit (End)",
6863+
]);
6864+
});
6865+
6866+
it("replaces each caption entirely when the new props are provided", () => {
6867+
const { container } = render(
6868+
<DatePicker
6869+
selectsRange
6870+
startDate={rangeStart}
6871+
endDate={rangeEnd}
6872+
onChange={jest.fn()}
6873+
showTimeInput
6874+
timeInputStartLabel="Startzeit"
6875+
timeInputEndLabel="Endzeit"
6876+
inline
6877+
/>,
6878+
);
6879+
expect(captionsOf(container)).toEqual(["Startzeit", "Endzeit"]);
6880+
});
6881+
6882+
it("overrides timeInputLabel + suffix when both are set", () => {
6883+
const { container } = render(
6884+
<DatePicker
6885+
selectsRange
6886+
startDate={rangeStart}
6887+
endDate={rangeEnd}
6888+
onChange={jest.fn()}
6889+
showTimeInput
6890+
timeInputLabel="Uhrzeit"
6891+
timeInputStartLabel="Von"
6892+
timeInputEndLabel="Bis"
6893+
inline
6894+
/>,
6895+
);
6896+
expect(captionsOf(container)).toEqual(["Von", "Bis"]);
6897+
});
6898+
6899+
it("falls back per-side when only one of the two props is set", () => {
6900+
const { container: c1 } = render(
6901+
<DatePicker
6902+
selectsRange
6903+
startDate={rangeStart}
6904+
endDate={rangeEnd}
6905+
onChange={jest.fn()}
6906+
showTimeInput
6907+
timeInputStartLabel="Von"
6908+
inline
6909+
/>,
6910+
);
6911+
expect(captionsOf(c1)).toEqual(["Von", "Time (End)"]);
6912+
6913+
const { container: c2 } = render(
6914+
<DatePicker
6915+
selectsRange
6916+
startDate={rangeStart}
6917+
endDate={rangeEnd}
6918+
onChange={jest.fn()}
6919+
showTimeInput
6920+
timeInputEndLabel="Bis"
6921+
inline
6922+
/>,
6923+
);
6924+
expect(captionsOf(c2)).toEqual(["Time (Start)", "Bis"]);
6925+
});
6926+
6927+
it("does not affect single (non-range) mode", () => {
6928+
const { container } = render(
6929+
<DatePicker
6930+
selected={rangeStart}
6931+
onChange={jest.fn()}
6932+
showTimeInput
6933+
timeInputStartLabel="Startzeit"
6934+
timeInputEndLabel="Endzeit"
6935+
timeInputLabel="Uhrzeit"
6936+
inline
6937+
/>,
6938+
);
6939+
expect(captionsOf(container)).toEqual(["Uhrzeit"]);
6940+
});
6941+
});
68246942
});
68256943

68266944
describe("Critical functions coverage - best in class", () => {

0 commit comments

Comments
 (0)