import { forwardRef, useMemo, type ReactNode } from 'react'; import { type TextInput, View, type ViewProps } from 'react-native'; import { InputField as InputFieldPrimitive, InputRoot as InputRootPrimitive, Slot, dataAttributes, mergeDataAttributes, type IInputFieldProps, type IInputProps, type IInputSlotProps, } from '@cdx-ui/primitives'; import { ParentContext, cn, useParentContext, useStyleContext } from '@cdx-ui/utils'; import { Icon, type IconProps } from '../Icon'; import { type InputVariantProps, inputFieldPlaceholderVariants, inputFieldVariants, inputIconVariants, inputRootVariants, inputSlotVariants, } from './styles'; const SCOPE = 'INPUT'; const useInputStyleContext = () => useStyleContext(SCOPE) as InputVariantProps; // ============================================================================= // INPUT (ROOT) // ============================================================================= export interface InputProps extends ViewProps, IInputFieldProps { /** Render the consumer's child element in place of the default `View` host. */ asChild?: boolean; /** Additional Uniwind/Tailwind classes, merged after the shell's own styles. */ className?: string; /** Shell content — typically `Input.Field` plus optional `Input.Slot`/`Input.Icon`. */ children?: ReactNode; /** * Visual style variant. * @default 'outline' */ variant?: 'outline'; /** * Height and padding scale. * @default 'default' */ size?: 'default' | 'small'; } const InputRoot = forwardRef( ( { asChild, variant = 'outline', size = 'default', className, children, style, ...props }, ref, ) => { const computedClassName = cn(inputRootVariants({ variant, size }), className); const parent = useParentContext(); const ctx = useMemo(() => ({ ...parent, [SCOPE]: { variant, size } }), [parent, variant, size]); return ( {children} ); }, ); InputRoot.displayName = 'Input'; // ============================================================================= // INPUT FIELD // ============================================================================= export type InputFieldProps = IInputProps & { /** Additional Uniwind/Tailwind classes, merged after the field's own styles. */ className?: string; /** Uniwind class applied to the placeholder text color (native `placeholderTextColor`). */ placeholderTextColorClassName?: string; }; const InputField = forwardRef( ({ className, placeholderTextColorClassName, style, ...rest }, ref) => { const { size } = useInputStyleContext(); const computedClassName = cn(inputFieldVariants({ size }), className); const computedPlaceholderColorClassName = cn( inputFieldPlaceholderVariants(), placeholderTextColorClassName, ); return ( ); }, ); InputField.displayName = 'Input.Field'; // ============================================================================= // INPUT SLOT — styled-only (Standard 3: no behavior beyond layout) // ============================================================================= export interface InputSlotProps extends ViewProps, IInputSlotProps { /** Render the consumer's child element in place of the default `View` host. */ asChild?: boolean; /** Additional Uniwind/Tailwind classes, merged after the slot's own styles. */ className?: string; /** Adornment content (icons, buttons, or adornment text). */ children?: ReactNode; } const InputSlot = forwardRef( ({ asChild, className, children, style, ...props }, ref) => { const computedClassName = cn(inputSlotVariants(), className); const Comp = asChild ? Slot : View; return ( {children} ); }, ); InputSlot.displayName = 'Input.Slot'; // ============================================================================= // INPUT ICON // ============================================================================= export interface InputIconProps extends Omit { /** Render the consumer's child element in place of the default icon host. */ asChild?: boolean; /** * Forge icon component to render. Required unless `asChild` is set, in which * case the consumer's substituted element is rendered instead. */ as?: IconProps['as']; } const InputIcon = ({ asChild, className, style, as, ...props }: InputIconProps) => { const { size } = useInputStyleContext(); const computedClassName = cn(inputIconVariants({ size }), className); if (asChild) { return ( ); } if (!as) { return null; } return ( ); }; InputIcon.displayName = 'Input.Icon'; // ============================================================================= // COMPOUND EXPORT // ============================================================================= type InputCompoundComponent = typeof InputRoot & { Field: typeof InputField; Slot: typeof InputSlot; Icon: typeof InputIcon; }; export const Input = Object.assign(InputRoot, { Field: InputField, Slot: InputSlot, Icon: InputIcon, }) as InputCompoundComponent;