import classNames from 'classnames' import * as React from 'react' import { TLTranslationKey } from '../../hooks/useTranslation/TLTranslationKey' import { useTranslation } from '../../hooks/useTranslation/useTranslation' import { TLUiIconType } from '../../icon-types' import { Icon } from './Icon' /** @public */ export interface InputProps { disabled?: boolean label?: TLTranslationKey icon?: TLUiIconType iconLeft?: TLUiIconType autofocus?: boolean autoselect?: boolean children?: any defaultValue?: string placeholder?: string onComplete?: (value: string) => void onValueChange?: (value: string) => void onCancel?: (value: string) => void onBlur?: (value: string) => void className?: string /** * Usually on iOS when you focus an input, the browser will adjust the viewport to bring the input * into view. Sometimes this doesn't work properly though - for example, if the input is newly * created, iOS seems to have a hard time adjusting the viewport for it. This prop allows you to * opt-in to some extra code to manually bring the input into view when the visual viewport of the * browser changes, but we don't want to use it everywhere because generally the native behavior * looks nicer in scenarios where it's sufficient. */ shouldManuallyMaintainScrollPositionWhenFocused?: boolean value?: string } /** @public */ export const Input = React.forwardRef(function Input( { className, label, icon, iconLeft, autoselect = false, autofocus = false, defaultValue, placeholder, onComplete, onValueChange, onCancel, onBlur, shouldManuallyMaintainScrollPositionWhenFocused = false, children, value, }, ref ) { const rInputRef = React.useRef(null) // combine rInputRef and ref React.useImperativeHandle(ref, () => rInputRef.current as HTMLInputElement) const msg = useTranslation() const rInitialValue = React.useRef(defaultValue ?? '') const rCurrentValue = React.useRef(defaultValue ?? '') const [isFocused, setIsFocused] = React.useState(false) const handleFocus = React.useCallback( (e: React.FocusEvent) => { setIsFocused(true) const elm = e.currentTarget as HTMLInputElement rCurrentValue.current = elm.value requestAnimationFrame(() => { if (autoselect) { elm.select() } }) }, [autoselect] ) const handleChange = React.useCallback( (e: React.ChangeEvent) => { const value = e.currentTarget.value rCurrentValue.current = value onValueChange?.(value) }, [onValueChange] ) const handleKeyUp = React.useCallback( (e: React.KeyboardEvent) => { switch (e.key) { case 'Enter': { e.currentTarget.blur() e.stopPropagation() onComplete?.(e.currentTarget.value) break } case 'Escape': { e.currentTarget.value = rInitialValue.current e.currentTarget.blur() e.stopPropagation() onCancel?.(e.currentTarget.value) break } } }, [onComplete, onCancel] ) const handleBlur = React.useCallback( (e: React.FocusEvent) => { setIsFocused(false) const value = e.currentTarget.value onBlur?.(value) }, [onBlur] ) React.useEffect(() => { const visualViewport = window.visualViewport if (isFocused && shouldManuallyMaintainScrollPositionWhenFocused && visualViewport) { const onViewportChange = () => { rInputRef.current?.scrollIntoView({ block: 'center' }) } visualViewport.addEventListener('resize', onViewportChange) visualViewport.addEventListener('scroll', onViewportChange) requestAnimationFrame(() => { rInputRef.current?.scrollIntoView({ block: 'center' }) }) return () => { visualViewport.removeEventListener('resize', onViewportChange) visualViewport.removeEventListener('scroll', onViewportChange) } } }, [isFocused, shouldManuallyMaintainScrollPositionWhenFocused]) return (
{children} {label && } {iconLeft && } {icon && }
) })