import { clsx } from 'clsx'; import React, { useCallback, useState } from 'react'; import { Field, FieldProps } from '../field'; import { FieldHint } from '../field-hint'; import { Input } from '../input'; import { InputLabel, useInputLabel } from '../input-label'; import { useCombinedRef, useIsFocused } from '../utils/hooks'; import * as css from './input-field.css'; /*-- Types --*/ type InputProps = React.ComponentProps; export interface InputFieldProps extends Omit< InputProps, 'startAddonRef' | 'endAddonRef' | 'placeholderVisible' | 'as' | 'full' > { label?: React.ReactNode; inputRef?: React.ForwardedRef; error?: boolean | React.ReactNode; hint?: React.ReactNode; /** * If `true`, it adds some space below the input so a layout doesn't jump * when a helper text (`error`, `hint`) appears. */ hasHelperTextSpace?: boolean; width?: FieldProps['width']; } /*-- Main --*/ export const InputField = React.forwardRef( function InputField( { id, value: userValue, defaultValue = '', onFocus: userOnFocus, onBlur: userOnBlur, onChange: userOnChange, inputRef: userInputRef, label, error, hint, placeholder, disabled, hasHelperTextSpace, className, width, ...rest }, ref, ) { const isControllable = userValue !== undefined; const [internalValue, setInternalValue] = useState(defaultValue); const value = isControllable ? userValue : internalValue; const { isFocused, onFocus, onBlur } = useIsFocused({ onFocus: userOnFocus, onBlur: userOnBlur, }); const { labelRef, startAddonRef, targetRef } = useInputLabel(); const combinedInputRef = useCombinedRef(userInputRef, targetRef); const onChange: React.ChangeEventHandler = useCallback( event => { if (!isControllable) { setInternalValue(event.target.value); } userOnChange && userOnChange(event); }, [isControllable, userOnChange], ); const shouldLabelShrink = !!value || isFocused; const isInvalid = !!error; const hintText = error && typeof error !== 'boolean' ? error : hint; return ( {label && ( {label} )} {hasHelperTextSpace ? ( {hintText} ) : ( hintText && {hintText} )} ); }, );