77 行
2.1 KiB
TypeScript
77 行
2.1 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) => {
|
|
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 }`
|
|
}
|
|
|
|
|
|
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 }
|
|
|
|
|
|
const DateTimeField: FC<Props> = ({ value, onChange, className, onBlur, invalid, ...rest }) => {
|
|
const [local, setLocal] = useState ('')
|
|
|
|
useEffect (() => {
|
|
setLocal (value ? toDateTimeLocalValue (new Date (value)) : '')
|
|
}, [value])
|
|
|
|
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={60}
|
|
value={local}
|
|
aria-invalid={invalid}
|
|
onChange={ev => {
|
|
const v = ev.target.value
|
|
setLocal (v)
|
|
onChange?.(v ? toMinutePrecisionIsoUtc (v) : null)
|
|
}}
|
|
onBlur={onBlur}/>)
|
|
}
|
|
|
|
export default DateTimeField
|
|
export { toMinutePrecisionIsoUtc }
|