diff --git a/ui-core/src/components/inputs/TimePicker.tsx b/ui-core/src/components/inputs/TimePicker.tsx index 0d13258c9..f3d3fda98 100644 --- a/ui-core/src/components/inputs/TimePicker.tsx +++ b/ui-core/src/components/inputs/TimePicker.tsx @@ -8,7 +8,17 @@ import InputModal from '../Modal'; export type TimePickerProps = InputProps & { hours?: number; minutes?: number; - onTimeChange: ({ hours, minutes }: { hours: number; minutes: number }) => void; + seconds?: number; + displaySeconds?: boolean; + onTimeChange: ({ + hours, + minutes, + seconds, + }: { + hours: number; + minutes: number; + seconds?: number; + }) => void; }; type TimeRangeProps = { @@ -32,7 +42,14 @@ const TimeRange = ({ range, selectedItem, className, onSelectItem }: TimeRangePr ); -const TimePicker = ({ onTimeChange, hours = 0, minutes = 0, ...otherProps }: TimePickerProps) => { +const TimePicker = ({ + onTimeChange, + hours = 0, + minutes = 0, + seconds = 0, + displaySeconds, + ...otherProps +}: TimePickerProps) => { const [isModalOpen, setIsModalOpen] = useState(false); const inputRef = useRef(null); @@ -42,28 +59,61 @@ const TimePicker = ({ onTimeChange, hours = 0, minutes = 0, ...otherProps }: Tim if (newMinutes >= 60) { newMinutes = 0; - newHours = newHours === 23 ? 0 : newHours + 1; + newHours = (newHours + 1) % 24; } else if (newMinutes < 0) { newMinutes = 59; - newHours = newHours === 0 ? 23 : newHours - 1; + newHours = (newHours + 23) % 24; // minus 1 hour } - onTimeChange({ hours: newHours, minutes: newMinutes }); + 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; + + 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 }); }; const handleChange = (event: React.ChangeEvent) => { const value = event.target.value; - const [h, m] = value.split(':'); - if (h !== undefined && m !== undefined) { - onTimeChange({ hours: parseInt(h), minutes: parseInt(m) }); + 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) }); + } } }; const formatTimeValue = (value: number, max: number) => Math.max(0, Math.min(max, value)).toString().padStart(2, '0'); - const selectedTime = `${formatTimeValue(hours, 23)}:${formatTimeValue(minutes, 59)}`; + const displayedSecondsPart = displaySeconds ? `:${formatTimeValue(seconds, 59)}` : ''; + const selectedTime = `${formatTimeValue(hours, 23)}:${formatTimeValue(minutes, 59)}${displayedSecondsPart}`; const hoursRange = [...Array(24).keys()]; - const minutesRange = [...Array(12).keys()].map((i) => i * 5); + const minutesAndSecondsRange = [...Array(12).keys()].map((i) => i * 5); const openModal = () => { setIsModalOpen(true); @@ -74,12 +124,14 @@ const TimePicker = ({ onTimeChange, hours = 0, minutes = 0, ...otherProps }: Tim return (
@@ -88,17 +140,17 @@ const TimePicker = ({ onTimeChange, hours = 0, minutes = 0, ...otherProps }: Tim range={hoursRange} selectedItem={hours} className="hour" - onSelectItem={(h) => onTimeChange({ hours: h, minutes })} + onSelectItem={(h) => onTimeChange({ hours: h, minutes, seconds })} />
:
onTimeChange({ hours: hours, minutes: m })} + onSelectItem={(m) => onTimeChange({ hours, minutes: m, seconds })} />
+ {displaySeconds && ( + <> +
:
+
+ onTimeChange({ hours, minutes, seconds: s })} + /> +
+ + +
+
+ + )}
diff --git a/ui-core/src/stories/TimePicker.stories.tsx b/ui-core/src/stories/TimePicker.stories.tsx index 4cfaf2615..b1e8526a0 100644 --- a/ui-core/src/stories/TimePicker.stories.tsx +++ b/ui-core/src/stories/TimePicker.stories.tsx @@ -9,19 +9,23 @@ import '@osrd-project/ui-core/dist/theme.css'; const TimePickerStory = (props: TimePickerProps) => { const [selectedHour, setSelectedHour] = useState(props.hours); const [selectedMinute, setSelectedMinute] = useState(props.minutes); - const onTimeChange = (newTime: { hours: number; minutes: number }) => { + const [selectedSecond, setSelectedSecond] = useState(props.seconds); + const onTimeChange = (newTime: { hours: number; minutes: number; seconds?: number }) => { setSelectedHour(newTime.hours); setSelectedMinute(newTime.minutes); + setSelectedSecond(newTime.seconds); }; useEffect(() => { setSelectedHour(props.hours); setSelectedMinute(props.minutes); - }, [props.hours, props.minutes]); + setSelectedSecond(props.seconds); + }, [props.hours, props.minutes, props.seconds]); return ( ); @@ -32,6 +36,7 @@ const meta: Meta = { args: { disabled: false, readOnly: false, + displaySeconds: false, }, argTypes: { hours: { @@ -53,13 +58,6 @@ const meta: Meta = { }, title: 'Core/TimePicker', tags: ['autodocs'], - decorators: [ - (Story) => ( -
- -
- ), - ], render: TimePickerStory, }; @@ -70,6 +68,13 @@ export const Default: Story = { args: { label: 'Time', }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], }; export const DisabledTimePicker: Story = { @@ -77,4 +82,25 @@ export const DisabledTimePicker: Story = { disabled: true, label: 'Time', }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const TimePickerWithSeconds: Story = { + args: { + displaySeconds: true, + label: 'Time', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], }; diff --git a/ui-core/src/styles/inputs/timePicker.css b/ui-core/src/styles/inputs/timePicker.css index 398a1d129..cf0200693 100644 --- a/ui-core/src/styles/inputs/timePicker.css +++ b/ui-core/src/styles/inputs/timePicker.css @@ -1,9 +1,11 @@ -.time-picker { - .time-input { - cursor: pointer; +@-moz-document url-prefix() { + .time-picker .time-input { + letter-spacing: -0.9px; } +} - input[type='time']::-webkit-calendar-picker-indicator { +.time-picker { + .time-input::-webkit-calendar-picker-indicator { background: none; display: none; } @@ -45,12 +47,14 @@ font-size: 1.5rem; } + .second-container, .minute-container { position: relative; height: 14.188rem; align-content: center; } + .second-buttons, .minute-buttons { display: flex; position: absolute; @@ -60,6 +64,7 @@ gap: 0.75rem; } + .second-button, .minute-button { cursor: pointer; display: block; @@ -83,11 +88,13 @@ } .hour, - .minute { + .minute, + .second { font-size: 1.125rem; font-weight: 600; text-align: center; - line-height: 1.5rem; + vertical-align: center; + line-height: 1.9rem; cursor: pointer; width: 2.875rem; height: 1.875rem;