5eb4543255
EventTimeInput24h switches to input type=time on touch devices while keeping dual selects on desktop for reliable 24h entry. Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import { useId, useMemo } from 'react'
|
|
import { joinTimeHHMM, splitTimeHHMM } from '../utils/logEntryPayload.js'
|
|
import { preferNativeCameraPicker } from '../utils/captureVideoFrame.js'
|
|
|
|
const HOURS = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0'))
|
|
const MINUTES = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0'))
|
|
|
|
interface EventTimeInput24hProps {
|
|
value: string
|
|
onChange: (value: string) => void
|
|
disabled?: boolean
|
|
'aria-label'?: string
|
|
}
|
|
|
|
export default function EventTimeInput24h({
|
|
value,
|
|
onChange,
|
|
disabled = false,
|
|
'aria-label': ariaLabel
|
|
}: EventTimeInput24hProps) {
|
|
const baseId = useId()
|
|
const useNativePicker = preferNativeCameraPicker()
|
|
const { hours, minutes } = useMemo(() => splitTimeHHMM(value), [value])
|
|
const timeValue = useMemo(() => joinTimeHHMM(hours, minutes), [hours, minutes])
|
|
|
|
if (useNativePicker) {
|
|
return (
|
|
<div className="time-input-24h">
|
|
<input
|
|
id={baseId}
|
|
type="time"
|
|
step={60}
|
|
className="input-text time-input-24h__native"
|
|
value={timeValue}
|
|
onChange={(e) => {
|
|
const next = e.target.value
|
|
if (next) onChange(next.slice(0, 5))
|
|
}}
|
|
disabled={disabled}
|
|
aria-label={ariaLabel}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="time-input-24h">
|
|
<select
|
|
id={`${baseId}-hours`}
|
|
className="input-text time-input-24h__select"
|
|
value={hours}
|
|
onChange={(e) => onChange(joinTimeHHMM(e.target.value, minutes))}
|
|
disabled={disabled}
|
|
aria-label={ariaLabel ? `${ariaLabel} (h)` : undefined}
|
|
>
|
|
{HOURS.map((hour) => (
|
|
<option key={hour} value={hour}>
|
|
{hour}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<span className="time-input-24h__sep" aria-hidden="true">
|
|
:
|
|
</span>
|
|
<select
|
|
id={`${baseId}-minutes`}
|
|
className="input-text time-input-24h__select"
|
|
value={minutes}
|
|
onChange={(e) => onChange(joinTimeHHMM(hours, e.target.value))}
|
|
disabled={disabled}
|
|
aria-label={ariaLabel ? `${ariaLabel} (min)` : undefined}
|
|
>
|
|
{MINUTES.map((minute) => (
|
|
<option key={minute} value={minute}>
|
|
{minute}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
)
|
|
}
|