Skip to content

Commit

Permalink
ui-core: fix time picker component's properties
Browse files Browse the repository at this point in the history
fix #810

This commit fix the allowed properties on the component, to avoid
confusion for example between ``value` and `hours` & `minutes`.

Also refacto :
- use `useCallback` when necessary
- static function shoud be outside component definition
- `otherProps` at top level, so even if a prop is defined and the
  component also use it, it will be overriden by the component

Signed-off-by: Benoit Simard <[email protected]>
  • Loading branch information
sim51 committed Jan 8, 2025
1 parent f74d1fb commit 539377e
Showing 1 changed file with 66 additions and 56 deletions.
122 changes: 66 additions & 56 deletions ui-core/src/components/inputs/TimePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React, { useState, useRef } from 'react';
import React, { useState, useRef, useCallback } from 'react';

import cx from 'classnames';

import Input, { type InputProps } from './Input';
import InputModal from '../Modal';

export type TimePickerProps = InputProps & {
export type TimePickerProps = Omit<
InputProps,
'type' | 'name' | 'value' | 'onClick' | 'onChange' | 'ref' | 'step'
> & {
hours?: number;
minutes?: number;
seconds?: number;
Expand Down Expand Up @@ -42,6 +45,10 @@ const TimeRange = ({ range, selectedItem, className, onSelectItem }: TimeRangePr
</div>
);

function formatTimeValue(value: number, max: number) {
return Math.max(0, Math.min(max, value)).toString().padStart(2, '0');
}

const TimePicker = ({
onTimeChange,
hours = 0,
Expand All @@ -53,86 +60,89 @@ const TimePicker = ({
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);

const incrementMinute = (increment: number) => {
let newMinutes = minutes + increment;
let newHours = hours;

if (newMinutes >= 60) {
newMinutes = 0;
newHours = (newHours + 1) % 24;
} else if (newMinutes < 0) {
newMinutes = 59;
newHours = (newHours + 23) % 24; // minus 1 hour
}
onTimeChange({ hours: newHours, minutes: newMinutes, seconds });
};

const incrementSeconds = (increment: number) => {
let newSeconds = seconds + increment;
let newMinutes = minutes;
let newHours = hours;

if (newSeconds >= 60) {
newSeconds = 0;
newMinutes += 1;
const incrementMinute = useCallback(
(increment: number) => {
let newMinutes = minutes + increment;
let newHours = hours;

if (newMinutes >= 60) {
newMinutes = 0;
newHours = (newHours + 1) % 24;
}
} else if (newSeconds < 0) {
newSeconds = 59;
newMinutes -= 1;

if (newMinutes < 0) {
} else if (newMinutes < 0) {
newMinutes = 59;
newHours = (newHours + 23) % 24; // minus 1 hour
}
}
onTimeChange({ hours: newHours, minutes: newMinutes, seconds: newSeconds });
};

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
if (!displaySeconds) {
const [h, m] = value.split(':');
if (h !== undefined && m !== undefined) {
onTimeChange({ hours: parseInt(h), minutes: parseInt(m) });
}
} else {
const [h, m, s] = value.split(':');
if (h !== undefined && m !== undefined && s !== undefined) {
onTimeChange({ hours: parseInt(h), minutes: parseInt(m), seconds: parseInt(s) });
onTimeChange({ hours: newHours, minutes: newMinutes, seconds });
},
[hours, minutes, seconds, onTimeChange]
);

const incrementSeconds = useCallback(
(increment: number) => {
let newSeconds = seconds + increment;
let newMinutes = minutes;
let newHours = hours;

if (newSeconds >= 60) {
newSeconds = 0;
newMinutes += 1;

if (newMinutes >= 60) {
newMinutes = 0;
newHours = (newHours + 1) % 24;
}
} else if (newSeconds < 0) {
newSeconds = 59;
newMinutes -= 1;

if (newMinutes < 0) {
newMinutes = 59;
newHours = (newHours + 23) % 24; // minus 1 hour
}
}
}
};
onTimeChange({ hours: newHours, minutes: newMinutes, seconds: newSeconds });
},
[hours, minutes, seconds, onTimeChange]
);

const formatTimeValue = (value: number, max: number) =>
Math.max(0, Math.min(max, value)).toString().padStart(2, '0');
const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
if (!displaySeconds) {
const [h, m] = value.split(':');
if (h !== undefined && m !== undefined) {
onTimeChange({ hours: parseInt(h), minutes: parseInt(m) });
}
} else {
const [h, m, s] = value.split(':');
if (h !== undefined && m !== undefined && s !== undefined) {
onTimeChange({ hours: parseInt(h), minutes: parseInt(m), seconds: parseInt(s) });
}
}
},
[displaySeconds, onTimeChange]
);

const displayedSecondsPart = displaySeconds ? `:${formatTimeValue(seconds, 59)}` : '';
const selectedTime = `${formatTimeValue(hours, 23)}:${formatTimeValue(minutes, 59)}${displayedSecondsPart}`;
const hoursRange = [...Array(24).keys()];
const minutesAndSecondsRange = [...Array(12).keys()].map((i) => i * 5);

const openModal = () => {
setIsModalOpen(true);
};

const closeModal = () => setIsModalOpen(false);
const openModal = useCallback(() => setIsModalOpen(true), []);
const closeModal = useCallback(() => setIsModalOpen(false), []);

return (
<div className="time-picker">
<Input
className={cx('input', 'time-input')}
{...otherProps}
className={cx('input', 'time-input', otherProps.className)}
type="time"
name="time"
value={selectedTime}
onClick={openModal}
onChange={handleChange}
ref={inputRef}
step={displaySeconds ? 1 : 60}
{...otherProps}
/>
<InputModal inputRef={inputRef} isOpen={isModalOpen} onClose={closeModal}>
<div className="time-picker-container">
Expand Down

0 comments on commit 539377e

Please sign in to comment.