Skip to content

Commit

Permalink
[@mantine/dates] DateTimePicker: Fix minDate and maxDate not bein…
Browse files Browse the repository at this point in the history
…g respected in time input (#5819)

* Add minTime and maxTime support to `TimeInput.tsx`

* Fixed TimePicker value and Min Max Date rules

* Fixed TimeInput formatting and added `WithMinMaxDates` story

* Changed story name
  • Loading branch information
snturk committed Feb 27, 2024
1 parent 08e581e commit 02d287b
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,26 @@ 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 (
<div style={{ padding: 40, maxWidth: 400 }}>
<DateTimePicker placeholder="Date time picker" withSeconds minDate={new Date()} />
<div style={{ marginBottom: 20 }}>
<div>Min date: {minDate.toLocaleString()}</div>
<div>Max date: {maxDate.toLocaleString()}</div>
</div>
<DateTimePicker
placeholder="Date time picker"
withSeconds
minDate={minDate}
maxDate={maxDate}
/>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export const DateTimePicker = factory<DateTimePickerFactory>((_props, ref) => {
variant,
dropdownType,
vars,
minDate,
maxDate,
...rest
} = props;

Expand Down Expand Up @@ -190,6 +192,9 @@ export const DateTimePicker = factory<DateTimePickerFactory>((_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 (
Expand All @@ -213,6 +218,8 @@ export const DateTimePicker = factory<DateTimePickerFactory>((_props, ref) => {
>
<DatePicker
{...calendarProps}
maxDate={maxDate}
minDate={minDate}
size={size}
variant={variant}
type="default"
Expand Down Expand Up @@ -248,6 +255,20 @@ export const DateTimePicker = factory<DateTimePickerFactory>((_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}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ export function WithSeconds() {
</div>
);
}

export function WithMinMaxTimes() {
return (
<div style={{ padding: 40, maxWidth: 400 }}>
<TimeInput minTime="10:00" maxTime="18:00" />
</div>
);
}
78 changes: 77 additions & 1 deletion packages/@mantine/dates/src/components/TimeInput/TimeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<{
Expand All @@ -33,22 +39,92 @@ const defaultProps: Partial<TimeInputProps> = {};

export const TimeInput = factory<TimeInputFactory>((_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<TimeInputFactory>({
classNames,
styles,
props,
});

/**
* 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 [hours, minutes, seconds] = val.split(':').map(Number);

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;
}
}
}

return 0;
};

const onTimeBlur = (event: React.FocusEvent<HTMLInputElement>) => {
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);
}
}
}
};

return (
<InputBase
classNames={{ ...resolvedClassNames, input: cx(classes.input, resolvedClassNames?.input) }}
styles={resolvedStyles}
unstyled={unstyled}
ref={ref}
value={value}
{...others}
step={withSeconds ? 1 : 60}
onChange={onChange}
onBlur={onTimeBlur}
type="time"
__staticSelector="TimeInput"
/>
Expand Down

0 comments on commit 02d287b

Please sign in to comment.