import classNames from 'classnames' import { type ChangeEvent, type FocusEventHandler, forwardRef, type InputHTMLAttributes, type MouseEventHandler, type MutableRefObject, useEffect, useImperativeHandle, useState, } from 'react' import { CloseIcon } from 'lib/icons/Close.js' export interface TextFieldProps { value?: string onChange?: (value: string) => void startAdornment?: JSX.Element endAdornment?: JSX.Element disabled?: boolean className?: string size?: 'small' | 'large' inputProps?: { ref?: | MutableRefObject | ((inputEl: HTMLInputElement) => void) } & Omit, 'value' | 'onChange'> clearable?: boolean onFocus?: FocusEventHandler onBlur?: FocusEventHandler onClick?: MouseEventHandler helperText?: string hasError?: boolean type?: 'text' | 'datetime-local' } export const TextField = forwardRef< HTMLInputElement | undefined, TextFieldProps >(function TextField( { value, onChange, className, startAdornment, endAdornment, inputProps, disabled = false, size = 'small', clearable = false, onFocus, onBlur, onClick, hasError = false, helperText, type = 'text', }, ref ): JSX.Element { const [inputEl, setInputEl] = useState(null) const valueIsEmpty = useValueIsEmpty(value, inputEl) const clearInput = (): void => { if (onChange != null) { onChange('') return } if (inputEl != null) { inputEl.value = '' } } // Maintain a local ref, and still forward it along useImperativeHandle(ref, () => inputEl ?? undefined, [inputEl]) const endAdornmentVisible = endAdornment != null || clearable return (
{startAdornment != null && (
{startAdornment}
)} {endAdornmentVisible && (
{clearable && ( )} {endAdornment}
)}
{helperText != null && (
{helperText}
)}
) }) export const handleString = (setter: (v: string) => void) => ( event: ChangeEvent< HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement > ): void => { setter(event.currentTarget.value) } function useValueIsEmpty( value: string | undefined, inputEl: HTMLInputElement | null ): boolean { const [valueIsEmpty, setValueIsEmpty] = useState(true) // If this is a controlled element, we'll just look at `value` useEffect(() => { setValueIsEmpty(value == null || value === '') }, [value]) // If this is not a controlled element, we'll need to listen to `input` // events. useEffect(() => { if (inputEl == null) { return } const handler = (event: Event): void => { if (value !== undefined) { return } if (event.target == null || !('value' in event.target)) { return } const inputValue = event.target.value if (value === undefined) { setValueIsEmpty(inputValue === '') } } inputEl.addEventListener('input', handler) return () => { inputEl.removeEventListener('input', handler) } }, [inputEl, value]) return valueIsEmpty }