ファイル
btrc-hub/frontend/src/components/common/DateTimeField.tsx
T
2026-10-26 12:19:43 +09:00

81 行
2.4 KiB
TypeScript

import { useEffect, useState } from 'react'
import { cn } from '@/lib/utils'
import type { ComponentPropsWithoutRef, FC, FocusEvent } from 'react'
const pad = (n: number): string => n.toString ().padStart (2, '0')
const toDateTimeLocalValue = (value: Date, seconds = false) => {
const y = value.getFullYear ()
const m = pad (value.getMonth () + 1)
const day = pad (value.getDate ())
const h = pad (value.getHours ())
const min = pad (value.getMinutes ())
return `${ y }-${ m }-${ day }T${ h }:${ min }${seconds ? `:${pad (value.getSeconds ())}` : ''}`
}
const toMinutePrecisionIsoUtc = (value: string) => {
const date = new Date (value)
if (Number.isNaN (date.getTime ()))
return value
const y = date.getUTCFullYear ()
const m = pad (date.getUTCMonth () + 1)
const day = pad (date.getUTCDate ())
const h = pad (date.getUTCHours ())
const min = pad (date.getUTCMinutes ())
return `${ y }-${ m }-${ day }T${ h }:${ min }Z`
}
type Props = Omit<ComponentPropsWithoutRef<'input'>, 'onChange'> & {
value?: string
onChange?: (isoUTC: string | null) => void
className?: string
onBlur?: (ev: FocusEvent<HTMLInputElement>) => void
invalid?: boolean
precision?: 'minute' | 'second' }
const DateTimeField: FC<Props> = props => {
const { value, onChange, className, onBlur, invalid, precision = 'minute', ...rest } = props
const [local, setLocal] = useState ('')
useEffect (() => {
setLocal (value ? toDateTimeLocalValue (new Date (value), precision === 'second') : '')
}, [value, precision])
return (
<input
{...rest}
className={cn ('border rounded p-2',
(invalid
? ['border-red-500 bg-red-50 text-red-900',
'focus:border-red-500 focus:outline-none',
'focus:ring-2 focus:ring-red-200',
'dark:border-red-500 dark:bg-red-950/30 dark:text-red-100']
: ['border-gray-300',
'focus:border-blue-500 focus:outline-none',
'focus:ring-2 focus:ring-blue-200']),
className)}
type="datetime-local"
step={precision === 'second' ? 1 : 60}
value={local}
aria-invalid={invalid}
onChange={ev => {
const v = ev.target.value
setLocal (v)
const iso = v ? (precision === 'second'
? new Date (v).toISOString () : toMinutePrecisionIsoUtc (v)) : null
onChange?.(iso)
}}
onBlur={onBlur}/>)
}
export default DateTimeField
export { toMinutePrecisionIsoUtc }