import React, { useCallback, useMemo, useRef, useState } from "react"; import Box, { BoxProps } from "../../atoms/box/box"; import { buildFontConfig } from "../../atoms/text/text"; import { useMolecularComponentConfig } from "../../../hooks/useMolecularComponentConfig"; import { useAtomicComponentConfig } from "../../../hooks/useAtomicComponentConfig"; import Icon from "../../atoms/icon/icon"; import { NativeSyntheticEvent, Platform, TextInput, TextInputChangeEventData, TextInputFocusEventData, TextInputProps, } from "react-native"; import { StyleFunctionContainer, ResponsiveValue, PaletteColors, MoleculeComponentProps, StateProps, } from "../../../theme/src/types"; import { boxStyleFunctions, colorStyleFunction, createStyleFunction, transformColorValue, typographyStyleFunction, } from "../../../theme/src/style-functions"; import { useStyleProps } from "../../../hooks/useStyleProps"; import Pressable from "../../atoms/pressable/pressable"; import { useFocusedState } from "../../../hooks/state/useFocusedState"; import { useDisabledState } from "../../../hooks/state/useDisabledState"; import { useInvalidState } from "../../../hooks/state/useInvalidState"; import { HStack } from "../../atoms/stack/stack"; import { useTheme } from "../../../hooks"; import { InputAtoms } from "./input.config"; import _ from "lodash"; export type BaseInputProps = Omit< TextInputProps, "placeholderTextColor" | "selectionColor" > & BoxProps & StateProps<"_focused" | "_disabled" | "_invalid"> & { /** * Whether the input field is disabled. * * @default false */ isDisabled?: boolean; /** * Whether the input field should span the entire width of the parent container. * * @default false */ isFullWidth?: boolean; /** * Whether there the input field is in an invalid state. * * @default false */ isInvalid?: boolean; /** * Whether the input field should display a clear button. * * @default false */ hasClearButton?: boolean; /** Icon to display on the left side of the text input */ leftIcon?: React.ReactElement; /** Icon to display on the right side of the text input */ rightIcon?: React.ReactElement; /** * Custom color of the placeholder text string. * * @default "neutral.500" */ placeholderTextColor?: ResponsiveValue; /** * Custom color of the highlight and cursor color. * * @default "primary.500" */ selectionColor?: ResponsiveValue; }; // Additional style Functions used for this component const placeholderTextColorStyleFunction = createStyleFunction({ property: "placeholderTextColor", styleProperty: "placeholderTextColor", themeKey: "palette", transform: transformColorValue, }); const selectionColorStyleFunction = createStyleFunction({ property: "selectionColor", styleProperty: "selectionColor", themeKey: "palette", transform: transformColorValue, }); const inputRootStyleFunction = [ ...boxStyleFunctions, placeholderTextColorStyleFunction, selectionColorStyleFunction, ]; const inputTextStyleFunction = [ colorStyleFunction, typographyStyleFunction, ] as StyleFunctionContainer[]; /** The Input component is a component that is used to get user input in a text field. **/ const Input = React.memo( React.forwardRef( ( { children, onFocus = undefined, onBlur = undefined, size = "m", isDisabled = false, isFullWidth = false, hasClearButton = false, isInvalid = false, leftIcon = undefined, rightIcon = undefined, colorScheme = "primary", onChangeText = () => {}, onChange = () => {}, ...rest }: Omit< MoleculeComponentProps<"Input", BaseInputProps, InputAtoms>, "atoms" > & { atoms?: Partial; }, textInputRef: any ) => { const { theme } = useTheme(); const [isCleared, setIsCleared] = useState( rest.value && rest.value.length > 0 ? false : true ); let molecularProps = useMolecularComponentConfig( "Input", rest, { size: size, variant: rest["variant"], }, colorScheme, boxStyleFunctions, "container", "input", "container" ); const { atoms } = molecularProps; const inputProps = useStyleProps(atoms.input, inputRootStyleFunction); const { placeholderTextColor, selectionColor, ...finalInputStyle } = inputProps.style; const textProps = useAtomicComponentConfig( "Text", atoms.text, { size: size, variant: atoms.text.variant, }, "primary", inputTextStyleFunction ); const memoizedBuildFontConfig = useCallback( () => buildFontConfig( textProps.style, atoms.input.allowFontScaling || true, theme ), [textProps.style, atoms.input.allowFontScaling] ); const finalTextStyles = useMemo( () => ({ ...textProps.style, ...memoizedBuildFontConfig(), }), [textProps.style, memoizedBuildFontConfig] ); // Use State for dynamic styles const { focused, setFocused, propsWithFocusedStyles } = useFocusedState( atoms.container, boxStyleFunctions, "molecule" ); atoms.container = propsWithFocusedStyles; const { propsWithDisabledStyles } = useDisabledState( atoms.container, boxStyleFunctions, "molecule", true, isDisabled ); atoms.container = propsWithDisabledStyles; const { propsWithInvalidStyles } = useInvalidState( atoms.container, boxStyleFunctions, "molecule", true, isInvalid ); atoms.container = propsWithInvalidStyles; // METHODS const clearInputHandler = () => { (textInputRef.current as any).clear(); setIsCleared(true); onChangeText(""); }; const onChangeHandler = ( event: NativeSyntheticEvent ) => { const { text } = event.nativeEvent; if (text.length > 0) { setIsCleared(false); } onChange(event); }; const onChangeTextHandler = (value: string) => { if (value.length > 0) { setIsCleared(false); } onChangeText(value); }; const focusInputHandler = ( event: NativeSyntheticEvent ) => { setFocused(true); if (onFocus) { onFocus(event); } }; const blurInputHandler = ( event: NativeSyntheticEvent ) => { setFocused(false); if (onBlur) { onBlur(event); } }; // Render Functions const renderLeftIcon = useCallback(() => { if (leftIcon) { return React.cloneElement(leftIcon, { ...atoms.icon, ...leftIcon.props, }); } }, [leftIcon, atoms.icon]); const renderRightIcon = useCallback(() => { if (rightIcon) { return React.cloneElement(rightIcon, { ...atoms.icon, ...rightIcon.props, }); } }, [rightIcon, atoms.icon]); const renderClearIcon = useCallback(() => { if (hasClearButton && !isCleared) { return ( ); } }, [hasClearButton, isCleared, clearInputHandler, atoms.icon]); return ( {renderLeftIcon()} {renderRightIcon()} {renderClearIcon()} ); } ) ); export type InputProps = React.ComponentProps; Input.displayName = "Input"; export default Input;