import React, { ReactElement, useState, useCallback, ChangeEvent, FocusEvent, useEffect, } from 'react'; import formatTime from 'date-fns/fp/format'; import isValid from 'date-fns/fp/isValid'; import parse from 'date-fns/fp/parse'; import css from '../../utils/css'; import Input from '../Input'; import PanelContent from './PanelContent'; import Dropdown from '../Dropdown'; import { IconName } from '../Icon'; import { CommonProps } from '../common'; import { TimePickerWrapper } from './StyledTimePicker'; export interface TimePickerProps extends Omit { /** * Specify the [automated assistance](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) in filling out form field values by the browser. */ autoComplete?: string; /** * Whether the picker is disabled. */ disabled?: boolean; /** * Time format. Following date-fns's format (https://date-fns.org/v2.16.1/docs/format). */ format?: string; /** * Id of element. */ id?: string; /** * Whether the input is invalid. */ invalid?: boolean; /** * Minute interval. */ minuteStep?: 1 | 5 | 10 | 15 | 20 | 30 | 60; /** * Name of element, is used to refer to the form data for submission. */ name?: string; /** * Blur event handler. */ onBlur?: (e: FocusEvent) => void; /** * onChange event handler. */ onChange?: (value: string) => void; /** * Placeholder text in the absence of any value. */ placeholder?: string; /** * Name of Icon or an Icon element to render on the left side of the input, before the user's cursor. */ prefix?: IconName | ReactElement; /** * The size of the input box. */ size?: 'small' | 'medium' | 'large'; /** * Current selected time which must be in correct format. */ value?: string; /** * Whether to use 12-hour system. This only works if `format` is not defined. */ with12Hours?: boolean; /** * Whether to show seconds. */ withSeconds?: boolean; } const generateFormat = ( withSeconds: boolean, with12Hours: boolean, format?: string ): string => { if (format !== undefined) return format; if (withSeconds === true) { if (with12Hours === true) return 'hh:mm:ss aa'; return 'HH:mm:ss'; } if (with12Hours === true) return 'hh:mm aa'; return 'HH:mm'; }; const TimePicker = ({ autoComplete, size = 'medium', onChange, onBlur, invalid = false, placeholder, prefix, disabled = false, value, format, withSeconds = false, with12Hours = false, name, id, className, style, sx = {}, 'data-test-id': dataTestId, minuteStep = 1, }: TimePickerProps): ReactElement => { const updatedFormat = generateFormat(withSeconds, with12Hours, format); const [pickedTime, setPickedTime] = useState(); const [open, setOpen] = useState(false); const closeDropdown = (): void => setOpen(false); useEffect(() => { // XXX: this setState logic is intentionally put here, not in OnInputChange // Since doing that would cause the cursor to jump to the end when setState is triggered if (value !== undefined) { const parsedChange = parse(new Date())(updatedFormat)(value); setPickedTime(isValid(parsedChange) ? parsedChange : undefined); } }, [updatedFormat, value]); const onInputChange = useCallback( (e: ChangeEvent): void => { const { target } = e; if (target.value !== undefined && onChange !== undefined) { onChange(target.value); } }, [onChange] ); const onSelectTime = useCallback( (selectedTime: Date): void => { if (onChange !== undefined) { setPickedTime(selectedTime); onChange(formatTime(updatedFormat, selectedTime)); } }, [onChange, updatedFormat] ); const formatHas12Hours = updatedFormat.includes('hh'); const formatHasSeconds = updatedFormat.includes('ss'); const timePickerDropdownContent = ( ); const timePickerDropdownTarget = ( setOpen(true)} id={id} name={name} autoComplete={autoComplete} /> ); return ( ); }; export default TimePicker;