From 2c9743615e16ca34f435262be8dcec53e0ec2129 Mon Sep 17 00:00:00 2001 From: snturk Date: Sun, 25 Feb 2024 01:51:32 +0300 Subject: [PATCH 1/4] Add minTime and maxTime support to `TimeInput.tsx` --- .../DateTimePicker/DateTimePicker.story.tsx | 13 +++- .../DateTimePicker/DateTimePicker.tsx | 21 ++++++ .../src/components/TimeInput/TimeInput.tsx | 64 ++++++++++++++++++- 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx b/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx index 067621b4849..aef8fbce5f8 100644 --- a/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx +++ b/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx @@ -41,9 +41,20 @@ export function WithSeconds() { } export function MinDate() { + const minDate = new Date(); + minDate.setHours(0, 30, 0, 0); + + const maxDate = new Date(); + maxDate.setDate(maxDate.getDate() + 5); + maxDate.setHours(22, 30, 0, 0); return (
- +
); } diff --git a/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.tsx b/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.tsx index 2664e64ba14..acef30f14c9 100644 --- a/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.tsx +++ b/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.tsx @@ -99,6 +99,8 @@ export const DateTimePicker = factory((_props, ref) => { variant, dropdownType, vars, + minDate, + maxDate, ...rest } = props; @@ -190,6 +192,9 @@ export const DateTimePicker = factory((_props, ref) => { } }, [dropdownOpened]); + const minTime = minDate ? dayjs(minDate).format('HH:mm:ss') : null; + const maxTime = maxDate ? dayjs(maxDate).format('HH:mm:ss') : null; + const __stopPropagation = dropdownType === 'popover'; return ( @@ -213,6 +218,8 @@ export const DateTimePicker = factory((_props, ref) => { > ((_props, ref) => { })} onChange={handleTimeChange} onKeyDown={handleTimeInputKeyDown} + minTime={ + _value && minDate && _value.toDateString() === minDate.toDateString() + ? minTime != null + ? minTime + : undefined + : undefined + } + maxTime={ + _value && maxDate && _value.toDateString() === maxDate.toDateString() + ? maxTime != null + ? maxTime + : undefined + : undefined + } size={size} data-mantine-stop-propagation={__stopPropagation || undefined} /> diff --git a/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx b/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx index 40a471ae385..afeb601b8f6 100644 --- a/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx +++ b/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx @@ -21,6 +21,12 @@ export interface TimeInputProps ElementProps<'input', 'size'> { /** Determines whether seconds input should be rendered */ withSeconds?: boolean; + + /** Minimum possible string time, if withSeconds is true, time should be in format HH:mm:ss, otherwise HH:mm */ + minTime?: string; + + /** Maximum possible string time, if withSeconds is true, time should be in format HH:mm:ss, otherwise HH:mm */ + maxTime?: string; } export type TimeInputFactory = Factory<{ @@ -33,7 +39,18 @@ const defaultProps: Partial = {}; export const TimeInput = factory((_props, ref) => { const props = useProps('TimeInput', defaultProps, _props); - const { classNames, styles, unstyled, vars, withSeconds, ...others } = props; + const { + classNames, + styles, + unstyled, + vars, + withSeconds, + minTime, + maxTime, + value, + onChange, + ...others + } = props; const { resolvedClassNames, resolvedStyles } = useResolvedStylesApi({ classNames, @@ -41,6 +58,50 @@ export const TimeInput = factory((_props, ref) => { props, }); + const onTimeChange = (event: React.ChangeEvent) => { + if (minTime !== undefined && maxTime !== undefined) { + const val = event.currentTarget.value; + + if (val) { + const [hours, minutes, seconds] = val.split(':').map(Number); + const isValid = + (hours.toString().length === 2 || hours === 0) && + (minutes.toString().length === 2 || minutes === 0) && + (withSeconds ? seconds.toString().length === 2 || seconds === 0 : true); + + if (isValid) { + if (minTime) { + const [minHours, minMinutes, minSeconds] = minTime.split(':').map(Number); + + if ( + hours < minHours || + (hours === minHours && minutes < minMinutes) || + (withSeconds && hours === minHours && minutes === minMinutes && seconds < minSeconds) + ) { + event.currentTarget.value = minTime; + } + } + + if (maxTime) { + const [maxHours, maxMinutes, maxSeconds] = maxTime.split(':').map(Number); + + if ( + hours > maxHours || + (hours === maxHours && minutes > maxMinutes) || + (withSeconds && hours === maxHours && minutes === maxMinutes && seconds > maxSeconds) + ) { + event.currentTarget.value = maxTime; + } + } + } + } + } + + if (onChange) { + onChange(event); + } + }; + return ( ((_props, ref) => { ref={ref} {...others} step={withSeconds ? 1 : 60} + onChange={onTimeChange} type="time" __staticSelector="TimeInput" /> From 1060800117f89e9c0d2b4bc85f38f927a7db48e5 Mon Sep 17 00:00:00 2001 From: snturk Date: Mon, 26 Feb 2024 18:16:17 +0300 Subject: [PATCH 2/4] Fixed TimePicker value and Min Max Date rules --- .../src/components/DateTimePicker/DateTimePicker.story.tsx | 7 ++++++- .../@mantine/dates/src/components/TimeInput/TimeInput.tsx | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx b/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx index aef8fbce5f8..799b8601fd3 100644 --- a/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx +++ b/packages/@mantine/dates/src/components/DateTimePicker/DateTimePicker.story.tsx @@ -40,15 +40,20 @@ export function WithSeconds() { ); } -export function MinDate() { +export function MinMaxDate() { const minDate = new Date(); minDate.setHours(0, 30, 0, 0); const maxDate = new Date(); maxDate.setDate(maxDate.getDate() + 5); maxDate.setHours(22, 30, 0, 0); + return (
+
+
Min date: {minDate.toLocaleString()}
+
Max date: {maxDate.toLocaleString()}
+
((_props, ref) => { }); const onTimeChange = (event: React.ChangeEvent) => { - if (minTime !== undefined && maxTime !== undefined) { + if (minTime !== undefined || maxTime !== undefined) { const val = event.currentTarget.value; if (val) { @@ -108,6 +108,7 @@ export const TimeInput = factory((_props, ref) => { styles={resolvedStyles} unstyled={unstyled} ref={ref} + value={value} {...others} step={withSeconds ? 1 : 60} onChange={onTimeChange} From 37893dcbbab795b3e8d2e56a9152d555d35bf711 Mon Sep 17 00:00:00 2001 From: snturk Date: Mon, 26 Feb 2024 22:12:18 +0300 Subject: [PATCH 3/4] Fixed TimeInput formatting and added `WithMinMaxDates` story --- .../components/TimeInput/TimeInput.story.tsx | 8 ++ .../src/components/TimeInput/TimeInput.tsx | 85 +++++++++++-------- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx b/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx index 3f7f0e22c76..08a1a560b93 100644 --- a/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx +++ b/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx @@ -18,3 +18,11 @@ export function WithSeconds() {
); } + +export function WithMinMaxDates() { + return ( +
+ +
+ ); +} diff --git a/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx b/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx index 27b127d1898..ec16525df93 100644 --- a/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx +++ b/packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx @@ -58,47 +58,59 @@ export const TimeInput = factory((_props, ref) => { props, }); - const onTimeChange = (event: React.ChangeEvent) => { + /** + * Check if time is within limits or not + * If the given value is within limits, return 0 + * If the given value is greater than the maxTime, return 1 + * If the given value is less than the minTime, return -1 + */ + const checkIfTimeLimitExceeded = (val: string) => { if (minTime !== undefined || maxTime !== undefined) { - const val = event.currentTarget.value; + const [hours, minutes, seconds] = val.split(':').map(Number); - if (val) { - const [hours, minutes, seconds] = val.split(':').map(Number); - const isValid = - (hours.toString().length === 2 || hours === 0) && - (minutes.toString().length === 2 || minutes === 0) && - (withSeconds ? seconds.toString().length === 2 || seconds === 0 : true); - - if (isValid) { - if (minTime) { - const [minHours, minMinutes, minSeconds] = minTime.split(':').map(Number); - - if ( - hours < minHours || - (hours === minHours && minutes < minMinutes) || - (withSeconds && hours === minHours && minutes === minMinutes && seconds < minSeconds) - ) { - event.currentTarget.value = minTime; - } - } - - if (maxTime) { - const [maxHours, maxMinutes, maxSeconds] = maxTime.split(':').map(Number); - - if ( - hours > maxHours || - (hours === maxHours && minutes > maxMinutes) || - (withSeconds && hours === maxHours && minutes === maxMinutes && seconds > maxSeconds) - ) { - event.currentTarget.value = maxTime; - } - } + if (minTime) { + const [minHours, minMinutes, minSeconds] = minTime.split(':').map(Number); + + if ( + hours < minHours || + (hours === minHours && minutes < minMinutes) || + (withSeconds && hours === minHours && minutes === minMinutes && seconds < minSeconds) + ) { + return -1; + } + } + + if (maxTime) { + const [maxHours, maxMinutes, maxSeconds] = maxTime.split(':').map(Number); + + if ( + hours > maxHours || + (hours === maxHours && minutes > maxMinutes) || + (withSeconds && hours === maxHours && minutes === maxMinutes && seconds > maxSeconds) + ) { + return 1; } } } - if (onChange) { - onChange(event); + return 0; + }; + + const onTimeBlur = (event: React.FocusEvent) => { + props.onBlur?.(event); + if (minTime !== undefined || maxTime !== undefined) { + const val = event.currentTarget.value; + + if (val) { + const check = checkIfTimeLimitExceeded(val); + if (check === 1) { + event.currentTarget.value = maxTime!; + props.onChange?.(event); + } else if (check === -1) { + event.currentTarget.value = minTime!; + props.onChange?.(event); + } + } } }; @@ -111,7 +123,8 @@ export const TimeInput = factory((_props, ref) => { value={value} {...others} step={withSeconds ? 1 : 60} - onChange={onTimeChange} + onChange={onChange} + onBlur={onTimeBlur} type="time" __staticSelector="TimeInput" /> From 7ba5dba1185d8527ec0f6e80630b0b7b8190cca6 Mon Sep 17 00:00:00 2001 From: snturk Date: Mon, 26 Feb 2024 22:17:36 +0300 Subject: [PATCH 4/4] Changed story name --- .../@mantine/dates/src/components/TimeInput/TimeInput.story.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx b/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx index 08a1a560b93..b4f01730b2c 100644 --- a/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx +++ b/packages/@mantine/dates/src/components/TimeInput/TimeInput.story.tsx @@ -19,7 +19,7 @@ export function WithSeconds() { ); } -export function WithMinMaxDates() { +export function WithMinMaxTimes() { return (