import classNames from 'classnames'; import type { EditorView } from 'codemirror'; import type { HTMLAttributes, ReactNode } from 'react'; import { forwardRef, useCallback, useMemo, useRef, useState } from 'react'; import type { EditorProps } from './Editor'; import { Editor } from './Editor'; import { IconButton } from './IconButton'; import { HStack, VStack } from './Stacks'; export type InputProps = Omit, 'onChange' | 'onFocus'> & Pick & { name: string; type?: 'text' | 'password'; label: string; hideLabel?: boolean; labelClassName?: string; containerClassName?: string; onChange?: (value: string) => void; onFocus?: () => void; onBlur?: () => void; defaultValue?: string; leftSlot?: ReactNode; rightSlot?: ReactNode; size?: 'sm' | 'md'; className?: string; placeholder?: string; autoFocus?: boolean; validate?: (v: string) => boolean; require?: boolean; }; export const Input = forwardRef(function Input( { label, type = 'text', hideLabel, className, containerClassName, labelClassName, onChange, placeholder, size = 'md', name, leftSlot, rightSlot, defaultValue, validate, require, onFocus, onBlur, forceUpdateKey, ...props }: InputProps, ref, ) { const [obscured, setObscured] = useState(type === 'password'); const [currentValue, setCurrentValue] = useState(defaultValue ?? ''); const [focused, setFocused] = useState(false); const handleFocus = useCallback(() => { setFocused(true); onFocus?.(); }, [onFocus]); const handleBlur = useCallback(() => { setFocused(false); onBlur?.(); }, [onBlur]); const id = `input-${name}`; const inputClassName = classNames( className, '!bg-transparent min-w-0 h-full w-full focus:outline-none placeholder:text-placeholder', // Bump things over if the slots are occupied leftSlot && 'pl-0.5 -ml-2', rightSlot && 'pr-0.5 -mr-2', ); const isValid = useMemo(() => { if (require && !validateRequire(currentValue)) return false; if (validate && !validate(currentValue)) return false; return true; }, [currentValue, validate, require]); const handleChange = useCallback( (value: string) => { setCurrentValue(value); onChange?.(value); }, [onChange], ); const wrapperRef = useRef(null); const handleSubmit = useCallback(() => { const form = wrapperRef.current?.closest('form'); if (!isValid || form == null) return; form?.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true })); }, [isValid]); return ( {leftSlot} {type === 'password' && ( setObscured((o) => !o)} /> )} {rightSlot} ); }); function validateRequire(v: string) { return v.length > 0; }