import { useImperativeHandle, forwardRef, useState, useRef } from "react"; import { TextInput as NativeTextInput, TouchableOpacity, Keyboard, Platform, View } from "react-native"; import type ITextInputProps from "./type"; import { type NumericInputInstance, type INumericInputRef, type NumericInputType } from "./type"; import stylesheet, { getNumericInputType, useStyles } from "./stylesheet"; import { NCoreUIKitLocalize, NCoreUIKitTheme } from "../../core/hooks"; import { type RefForwardingComponent } from "../../types"; import type ITextProps from "../text/type"; import { BadgeQuestionMark as BadgeQuestionMarkIcon, BadgeAlert as BadgeAlertIcon, BadgeCheck as BadgeCheckIcon, BadgeInfo as BadgeInfoIcon, EyeClosed as EyeClosedIcon, CircleX as CircleXIcon, BadgeX as BadgeXIcon, type LucideIcon, Eye as EyeIcon } from "lucide-react-native"; import Text from "../text"; const NumericInputTypeIcon: Record, LucideIcon> = { "question": BadgeQuestionMarkIcon, "success": BadgeCheckIcon, "warning": BadgeAlertIcon, "info": BadgeInfoIcon, "danger": BadgeXIcon }; const NumericInput: RefForwardingComponent = ({ isAutoKeyboardDismissOnBlur = true, rightIcon: RightIconComponentProp, hintTextIcon: HintTextIconProp, spreadBehaviour = "baseline", isShowHideTextButton = true, isWorkWithDebouncer = false, isShowHintTextIcon = false, icon: IconComponentProp, hintTextContainerStyle, isCleanEnabled = false, contentContainerStyle, subTitle = "Optional", onFocus: onFocusProp, isRequired = false, isDisabled = false, onBlur: onBlurProp, hideTextIconStyle, variant = "text", type = "default", rightIconOnPress, customLocalize, rightIconStyle, isShowSubTitle, cleanIconStyle, onChangeText, initialValue, customTheme, placeholder, iconOnPress, isOptional, validation, inputStyle, iconStyle, hintText, style, title, ...props }, ref) => { const { inlineSpaces, typography, radiuses, borders, spaces, colors } = NCoreUIKitTheme.useContext(customTheme); const { localize } = NCoreUIKitLocalize.useContext(customLocalize); const currentType = getNumericInputType({ type }); const inputDebouncer = useRef>(null); const inputRef = useRef(null); const styleType = type === "default" ? "neutral" : type === "question" ? "neutral" : type === "danger" ? "error" : type; const [ value, setValue ] = useState(initialValue ? initialValue : ""); const [ hideValue, setHideValue ] = useState(true); const [ isFocused, setIsFocused ] = useState(false); const [ selection, setSelection ] = useState({ start: 0, end: 0 }); const { hideTextIconContainer: hideTextIconContainerDynamicStyle, titleContainer: titleContainerDynamicStyle, hintTextIcon: hintTextIconDynamicStyle, cleanButton: cleanButtonDynamicStyle, rightIcon: rightIconDynamicStyle, container: containerDynamicStyle, hintText: hintTextDynamicStyle, required: requiredDynamicStyle, subTitle: subTitleDynamicStyle, content: contentDynamicStyle, overlay: overlayDynamicStyle, title: titleDynamicStyle, input: inputDynamicStyle, icon: iconDynamicStyle } = useStyles({ icon: IconComponentProp ? true : false, spreadBehaviour, inlineSpaces, currentType, isDisabled, typography, isFocused, radiuses, borders, spaces, colors, title, type }); useImperativeHandle( ref, () => ({ updateValue, cleanText, focus, blur }), [] ); const titleProps: ITextProps = { color: currentType.titleColor, variant: "bodyLargeSize" }; const iconProps: NCoreUIKit.IconCallbackProps = { size: Number(typography.labelLargeSize.fontSize) + 6, color: currentType.iconColor }; if(isDisabled) { iconProps.color = "disabled"; } const blur = () => { inputRef.current?.blur(); }; const focus = () => { inputRef.current?.focus(); }; const cleanText = () => { setValue(""); inputRef.current?.clear(); if(onChangeText) { onChangeText(""); } }; const updateValue = (text: string) => { setValue(text); inputRef.current?.setNativeProps({ text: text }); }; const onFocus = () => { setIsFocused(true); if(onFocusProp) onFocusProp(); }; const onBlur = () => { setIsFocused(false); inputRef.current?.blur(); if(isAutoKeyboardDismissOnBlur) { Keyboard.dismiss(); } if(onBlurProp) onBlurProp(); }; const toggleValueVisibility = () => { setHideValue(prevState => !prevState); }; const handleTextChange = (text: string) => { if ( props.maxLength && selection.start >= value.length && text.length > props.maxLength && text.length > value.length ) { inputRef.current?.setNativeProps({ value: value }); return; } const cleanedText = text.replace(/\n/g, "").replace(/[^0-9]/g, ""); const newValue = props.maxLength ? cleanedText.slice(0, props.maxLength) : cleanedText; if(text === "") { setValue(""); if(onChangeText) onChangeText(""); return; } const isValid = validation ? validation(newValue, (text) => { if (onChangeText) onChangeText(newValue); setValue(text); }) : true; if (!isValid) { setSelection({ start: selection.start, end: selection.end }); return; } const isDeleting = text.length < value.length; let nextPosition = selection.start; if (isDeleting) { nextPosition = Math.max(0, selection.start - 1); } else { nextPosition = Math.min(newValue.length, selection.start + 1); } setValue(newValue); if (onChangeText) onChangeText(newValue); setSelection({ start: nextPosition, end: nextPosition }); }; const renderCleanButton = () => { if(isDisabled) { return null; } if(variant !== "text") { return null; } if(!isCleanEnabled || !value.length) { return null; } return { if(inputRef.current) inputRef.current.clear(); if(onChangeText) { onChangeText(""); } setValue(""); }} > ; }; const renderIcon = () => { if (!IconComponentProp) { return null; } return ; }; const renderRightIcon = () => { if (!RightIconComponentProp) { return null; } if(isShowHideTextButton && variant === "hidden") { return null; } if(isCleanEnabled && value.length > 0 && variant === "text") { return null; } return ; }; const renderHideTextIcon = () => { if(!isShowHideTextButton) { return null; } if (variant !== "hidden") { return null; } return { hideValue ? : } ; }; const renderHintIcon = () => { if(!isShowHintTextIcon) { return null; } if(HintTextIconProp) { return ; } const CurrentHintIcon = NumericInputTypeIcon[type === "default" ? "question" : type]; let hintIconColor = colors.content.icon[currentType.hintTextIconColor]; if(isDisabled) { hintIconColor = colors.system.state.content.disabled[styleType]; } return ; }; const renderHintText = () => { if (!hintText) { return null; } return {renderHintIcon()} {hintText} ; }; const renderRequired = () => { if(!isRequired) { return null; } return * ; }; const renderSubtitle = () => { if(!isShowSubTitle && !isOptional) { return null; } return ( {isOptional ? localize("is-optional") : subTitle} ) ; }; const renderTitle = () => { if (!title) { return null; } return {renderRequired()} {title} {renderSubtitle()} ; }; const renderInput = () => { return setSelection(e.nativeEvent.selection)} secureTextEntry={variant === "hidden" && hideValue} underlineColorAndroid="rgba(255,255,255,0)" onChangeText={(text) => { if(isWorkWithDebouncer) { if(inputDebouncer.current) { clearTimeout(inputDebouncer.current); inputDebouncer.current = null; } inputDebouncer.current = setTimeout(() => {{ handleTextChange(text); }}, 550); } else { handleTextChange(text); } }} placeholder={placeholder} allowFontScaling={false} editable={!isDisabled} maxLength={undefined} selection={selection} multiline={false} onFocus={onFocus} onBlur={onBlur} value={value} ref={inputRef} style={[ inputStyle, stylesheet.input, inputDynamicStyle ]} />; }; const renderOverlay = () => { return ; }; return { if(!isDisabled) inputRef.current?.focus(); }} {...Platform.select({ web: { dataSet: { disablePressableDownEffect: "true" } }, default: {} })} > {renderTitle()} {renderIcon()} {renderInput()} {renderCleanButton()} {renderHideTextIcon()} {renderRightIcon()} {renderOverlay()} {renderHintText()} ; }; export default forwardRef(NumericInput);