'use client' import type { PropsWithChildren } from 'react' import { useCallback, useEffect } from 'react' import { FormProvider, useForm, useFormContext, type UseFormReturn } from 'react-hook-form' import dynamic from 'next/dynamic' import type { CustomInputProps, InputKind } from '@/components/formElements/input/inputTypes' import { useRemoteSelectOptions } from '@/components/formElements/input/useRemoteSelectOptions' import TextFieldControl from '@/components/formElements/input/TextFieldControl' import ChoiceFieldControl from '@/components/formElements/input/ChoiceFieldControl' import { texts, format } from '@/texts' import useWindowSize from '@/hooks/useWindowSize' const DateFieldControl = dynamic(() => import('@/components/formElements/input/DateFieldControl'), { ssr: false }) const EventScheduleFieldControl = dynamic(() => import('@/components/events/schedule/EventScheduleFieldControl'), { ssr: false, }) const JalaliBirthDateFieldControl = dynamic(() => import('@/components/formElements/input/JalaliBirthDateFieldControl'), { ssr: false, }) function defaultValueForKind(kind: InputKind): unknown { if (kind === 'checkbox' || kind === 'switch') return false if (kind === 'numberInput') return undefined return '' } function useOptionalFormContext(): UseFormReturn | null { const context = useFormContext() as UseFormReturn | null return context?.control ? context : null } type BoundProps = PropsWithChildren function FormBoundInput({ generalType, inputType, variant = 'bordered', label, name, placeholder: outerPlaceholder, description, className, iconStart, iconEnd, selectKey = 'code', selectValue = 'name', filterName = 'title', filterValue = '', apiField = 'data', searchMode = 'api', direction = 'rtl', pageSize = 20, otpLength = 4, url, size, radius = 'md', autoFocus, disabled, required, readonly, eventPickerStyle = 'default', multiple, onlyYearPicker, minValue, maxValue, formatOptions, maxDate, minDate = '1300/1/1', textAreaMinRows = 5, selectOptions, radioOptions, isOutsideFilter, ariaLabel, inputWrapper, labelIcon = null, isClearable = true, englishDigitsOnly = false, onBlur, onClear, onKeyDown, orientation = 'horizontal', }: BoundProps) { const { width } = useWindowSize() const { control, getValues } = useFormContext() const defaultInputWrapper = variant === 'bordered' ? // InputGroup focuses via :focus-within; TextArea via :focus / data-focused. // Invalid: single red border only (global CSS kills HeroUI invalid-field-ring outline). 'shadow-none border hover:!border-primary-300 focus:!border-primary focus-within:!border-primary data-[focused=true]:!border-primary data-[focus-within=true]:!border-primary data-[invalid=true]:!border-fourth data-[invalid=true]:hover:!border-fourth data-[invalid=true]:focus-within:!border-fourth' : 'shadow-none' const finalInputWrapper = inputWrapper || defaultInputWrapper const isChoiceField = generalType === 'select' || generalType === 'combobox' const placeholder = outerPlaceholder ?? (isChoiceField ? undefined : typeof label === 'string' ? format(texts.common.enterField, { label }) : '') const getCurrentValue = useCallback(() => getValues(name), [getValues, name]) const { loading, options: searchOptions, search: handleComboboxChange, } = useRemoteSelectOptions({ apiField, disabled, enabled: generalType === 'combobox', filterName, filterValue, getCurrentValue, isOutsideFilter, pageSize, searchMode, selectKey, selectValue, url, }) switch (generalType) { case 'input': case 'numberInput': return ( ) case 'otp': case 'combobox': return ( ) case 'textarea': return ( ) case 'select': case 'radio': case 'checkbox': case 'switch': return ( ) case 'datePickerPro': case 'datePicker': case 'timePicker': return ( ) case 'eventDatePicker': case 'eventTimePicker': return ( ) case 'birthDatePicker': return ( ) default: return null } } /** * Standalone / controlled bridge: keeps one public Input API without requiring * the caller to set up FormProvider for simple local state. */ function StandaloneInput(props: BoundProps) { const { name, generalType, value, onValueChange } = props const methods = useForm({ defaultValues: { [name]: value ?? defaultValueForKind(generalType) }, values: value !== undefined ? { [name]: value } : undefined, }) useEffect(() => { if (!onValueChange) return const subscription = methods.watch((values, info) => { if (info.name && info.name !== name) return onValueChange(values[name]) }) return () => { subscription.unsubscribe() } }, [methods, name, onValueChange]) return ( ) } /** * App-facing form field. Prefer this over `@/components/heroui/*` controls. * - Inside FormProvider: binds via `name` (react-hook-form) * - Outside FormProvider (or with `value` / `onValueChange`): works standalone */ const Input = (props: BoundProps) => { const form = useOptionalFormContext() const isControlled = props.value !== undefined || props.onValueChange !== undefined // Outside FormProvider, or explicitly controlled from parent state → standalone bridge. if (!form || isControlled) { return } return } export default Input