import classNames from 'classnames'; import type { FocusEvent, HTMLAttributes } from 'react'; import { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react'; import { useStateWithDeps } from '../../hooks/useStateWithDeps'; import { IconButton } from './IconButton'; import type { InputProps } from './Input'; import { Label } from './Label'; import { HStack } from './Stacks'; export type PlainInputProps = Omit & Pick, 'onKeyDownCapture'> & { onFocusRaw?: HTMLAttributes['onFocus']; type?: 'text' | 'password' | 'number'; step?: number; hideObscureToggle?: boolean; }; export const PlainInput = forwardRef<{ focus: () => void }, PlainInputProps>(function PlainInput( { autoFocus, autoSelect, className, containerClassName, defaultValue, forceUpdateKey, help, hideLabel, hideObscureToggle, label, labelClassName, labelPosition = 'top', leftSlot, name, onBlur, onChange, onFocus, onFocusRaw, onKeyDownCapture, onPaste, placeholder, required, rightSlot, size = 'md', tint, type = 'text', validate, }, ref, ) { const [obscured, setObscured] = useStateWithDeps(type === 'password', [type]); const [focused, setFocused] = useState(false); const [hasChanged, setHasChanged] = useStateWithDeps(false, [forceUpdateKey]); const inputRef = useRef(null); useImperativeHandle<{ focus: () => void } | null, { focus: () => void } | null>( ref, () => inputRef.current, ); const textareaRef = useRef(null); const handleFocus = useCallback( (e: FocusEvent) => { onFocusRaw?.(e); setFocused(true); if (autoSelect) { inputRef.current?.select(); textareaRef.current?.select(); } onFocus?.(); }, [autoSelect, onFocus, onFocusRaw], ); const handleBlur = useCallback(() => { setFocused(false); onBlur?.(); }, [onBlur]); const id = `input-${name}`; const commonClassName = classNames( className, '!bg-transparent min-w-0 w-full focus:outline-none placeholder:text-placeholder', 'px-2 text-xs font-mono cursor-text', ); const handleChange = useCallback( (value: string) => { onChange?.(value); setHasChanged(true); const isValid = (value: string) => { if (required && !validateRequire(value)) return false; if (typeof validate === 'boolean') return validate; if (typeof validate === 'function' && !validate(value)) return false; return true; }; inputRef.current?.setCustomValidity(isValid(value) ? '' : 'Invalid value'); }, [onChange, required, setHasChanged, validate], ); const wrapperRef = useRef(null); return (
{tint != null && (
)} {leftSlot} handleChange(e.target.value)} onPaste={(e) => onPaste?.(e.clipboardData.getData('Text'))} className={classNames(commonClassName, 'h-full')} onFocus={handleFocus} onBlur={handleBlur} required={required} autoFocus={autoFocus} placeholder={placeholder} onKeyDownCapture={onKeyDownCapture} /> {type === 'password' && !hideObscureToggle && ( setObscured((o) => !o)} /> )} {rightSlot}
); }); function validateRequire(v: string) { return v.length > 0; }