| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- 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<Exclude<NumericInputType, "default">, LucideIcon> = {
- "question": BadgeQuestionMarkIcon,
- "success": BadgeCheckIcon,
- "warning": BadgeAlertIcon,
- "info": BadgeInfoIcon,
- "danger": BadgeXIcon
- };
- const NumericInput: RefForwardingComponent<INumericInputRef, ITextInputProps> = ({
- 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<ReturnType<typeof setTimeout>>(null);
- const inputRef = useRef<NumericInputInstance | null>(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 <TouchableOpacity
- style={[
- cleanIconStyle,
- stylesheet.cleanButton,
- cleanButtonDynamicStyle
- ]}
- onPress={() => {
- if(inputRef.current) inputRef.current.clear();
- if(onChangeText) {
- onChangeText("");
- }
- setValue("");
- }}
- >
- <CircleXIcon
- color={colors.content.icon[currentType.iconColor]}
- size={20}
- />
- </TouchableOpacity>;
- };
- const renderIcon = () => {
- if (!IconComponentProp) {
- return null;
- }
- return <TouchableOpacity
- onPress={iconOnPress}
- style={[
- iconStyle,
- stylesheet.icon,
- iconDynamicStyle
- ]}
- >
- <IconComponentProp
- color={iconProps.color}
- size={iconProps.size}
- />
- </TouchableOpacity>;
- };
- const renderRightIcon = () => {
- if (!RightIconComponentProp) {
- return null;
- }
- if(isShowHideTextButton && variant === "hidden") {
- return null;
- }
- if(isCleanEnabled && value.length > 0 && variant === "text") {
- return null;
- }
- return <TouchableOpacity
- onPress={rightIconOnPress}
- style={[
- rightIconStyle,
- stylesheet.rightIcon,
- rightIconDynamicStyle
- ]}
- >
- <RightIconComponentProp
- color={iconProps.color}
- size={iconProps.size}
- />
- </TouchableOpacity>;
- };
- const renderHideTextIcon = () => {
- if(!isShowHideTextButton) {
- return null;
- }
- if (variant !== "hidden") {
- return null;
- }
- return <TouchableOpacity
- onPress={toggleValueVisibility}
- style={[
- hideTextIconStyle,
- stylesheet.hideTextIconContainer,
- hideTextIconContainerDynamicStyle
- ]}
- >
- {
- hideValue ?
- <EyeIcon
- color={colors.content.icon[iconProps.color]}
- size={20}
- />
- :
- <EyeClosedIcon
- color={colors.content.icon[iconProps.color]}
- size={20}
- />
- }
- </TouchableOpacity>;
- };
- const renderHintIcon = () => {
- if(!isShowHintTextIcon) {
- return null;
- }
- if(HintTextIconProp) {
- return <HintTextIconProp
- color={isDisabled ? "disabled" : currentType.hintTextIconColor}
- size={20}
- style={[
- stylesheet.hintTextIcon,
- hintTextIconDynamicStyle
- ]}
- />;
- }
- const CurrentHintIcon = NumericInputTypeIcon[type === "default" ? "question" : type];
- let hintIconColor = colors.content.icon[currentType.hintTextIconColor];
- if(isDisabled) {
- hintIconColor = colors.system.state.content.disabled[styleType];
- }
- return <CurrentHintIcon
- color={hintIconColor}
- size={20}
- style={[
- stylesheet.hintTextIcon,
- hintTextIconDynamicStyle
- ]}
- />;
- };
- const renderHintText = () => {
- if (!hintText) {
- return null;
- }
- return <View
- style={[
- hintTextContainerStyle,
- stylesheet.hintText,
- hintTextDynamicStyle
- ]}
- >
- {renderHintIcon()}
- <Text
- customColor={isDisabled ? colors.system.state.content.disabled[styleType] : undefined}
- color={currentType.hintTextColor}
- variant="labelSmallSize"
- >
- {hintText}
- </Text>
- </View>;
- };
- const renderRequired = () => {
- if(!isRequired) {
- return null;
- }
- return <Text
- color="danger"
- style={[
- stylesheet.required,
- requiredDynamicStyle
- ]}
- >
- *
- </Text>;
- };
- const renderSubtitle = () => {
- if(!isShowSubTitle && !isOptional) {
- return null;
- }
- return <Text
- variant="labelLargeSize"
- color={titleProps.color}
- style={[
- stylesheet.subTitle,
- subTitleDynamicStyle
- ]}
- >
- ( {isOptional ? localize("is-optional") : subTitle} )
- </Text>;
- };
- const renderTitle = () => {
- if (!title) {
- return null;
- }
- return <View
- style={[
- stylesheet.titleContainer,
- titleContainerDynamicStyle
- ]}
- >
- {renderRequired()}
- <Text
- {...titleProps}
- variant={titleProps.variant}
- color={titleProps.color}
- style={[
- stylesheet.title,
- titleDynamicStyle
- ]}
- >
- {title}
- </Text>
- {renderSubtitle()}
- </View>;
- };
- const renderInput = () => {
- return <NativeTextInput
- {...props}
- placeholderTextColor={colors.content.text[currentType.placeholderColor]}
- onSelectionChange={(e) => 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 <View
- style={[
- stylesheet.overlay,
- overlayDynamicStyle
- ]}
- />;
- };
- return <TouchableOpacity
- disabled={isDisabled}
- style={[
- style,
- stylesheet.container,
- containerDynamicStyle
- ]}
- onPress={() => {
- if(!isDisabled) inputRef.current?.focus();
- }}
- {...Platform.select({
- web: {
- dataSet: {
- disablePressableDownEffect: "true"
- }
- },
- default: {}
- })}
- >
- {renderTitle()}
- <View
- style={[
- contentContainerStyle,
- stylesheet.content,
- contentDynamicStyle
- ]}
- >
- {renderIcon()}
- {renderInput()}
- {renderCleanButton()}
- {renderHideTextIcon()}
- {renderRightIcon()}
- {renderOverlay()}
- </View>
- {renderHintText()}
- </TouchableOpacity>;
- };
- export default forwardRef(NumericInput);
|