diff --git a/src/OtpInput/OtpInput.tsx b/src/OtpInput/OtpInput.tsx index f1efc5d..a723979 100644 --- a/src/OtpInput/OtpInput.tsx +++ b/src/OtpInput/OtpInput.tsx @@ -1,131 +1,136 @@ import * as React from "react"; import { forwardRef, useImperativeHandle } from "react"; import { Platform, Pressable, Text, TextInput, View } from "react-native"; -import { styles } from "./OtpInput.styles"; -import { OtpInputProps, OtpInputRef } from "./OtpInput.types"; -import { VerticalStick } from "./VerticalStick"; + import { useOtpInput } from "./useOtpInput"; +import { VerticalStick } from "./VerticalStick"; +import { OtpInputProps, OtpInputRef } from "./OtpInput.types"; +import { styles } from "./OtpInput.styles"; + +export const OtpInput = forwardRef( + ({ defaultValue = "", ...props }, ref) => { + const { + models: { text, inputRef, focusedInputIndex, isFocused, placeholder }, + actions: { clear, handlePress, handleTextChange, focus, handleFocus, handleBlur }, + forms: { setTextWithRef }, + } = useOtpInput({ defaultValue, ...props }); + + const { + disabled, + numberOfDigits = 6, + autoFocus = true, + hideStick, + focusColor = "#A4D0A4", + focusStickBlinkingDuration, + secureTextEntry = false, + theme = {}, + textInputProps, + textProps, + type = "numeric", + } = props; -export const OtpInput = forwardRef((props, ref) => { - const { - models: { text, inputRef, focusedInputIndex, isFocused, placeholder }, - actions: { clear, handlePress, handleTextChange, focus, handleFocus, handleBlur, blur }, - forms: { setTextWithRef }, - } = useOtpInput(props); - const { - disabled, - numberOfDigits = 6, - autoFocus = true, - hideStick, - focusColor = "#A4D0A4", - focusStickBlinkingDuration, - secureTextEntry = false, - theme = {}, - textInputProps, - textProps, - type = "numeric", - } = props; - const { - containerStyle, - inputsContainerStyle, - pinCodeContainerStyle, - pinCodeTextStyle, - focusStickStyle, - focusedPinCodeContainerStyle, - filledPinCodeContainerStyle, - disabledPinCodeContainerStyle, - placeholderTextStyle, - } = theme; + const { + containerStyle, + inputsContainerStyle, + pinCodeContainerStyle, + pinCodeTextStyle, + focusStickStyle, + focusedPinCodeContainerStyle, + filledPinCodeContainerStyle, + disabledPinCodeContainerStyle, + placeholderTextStyle, + } = theme; - useImperativeHandle(ref, () => ({ clear, focus, setValue: setTextWithRef, blur })); + useImperativeHandle(ref, () => ({ clear, focus, setValue: setTextWithRef })); - const generatePinCodeContainerStyle = (isFocusedContainer: boolean, char: string) => { - const stylesArray = [styles.codeContainer, pinCodeContainerStyle]; - if (focusColor && isFocusedContainer) { - stylesArray.push({ borderColor: focusColor }); - } + const generatePinCodeContainerStyle = (isFocusedContainer: boolean, char: string) => { + const stylesArray = [styles.codeContainer, pinCodeContainerStyle]; + if (focusColor && isFocusedContainer) { + stylesArray.push({ borderColor: focusColor }); + } - if (focusedPinCodeContainerStyle && isFocusedContainer) { - stylesArray.push(focusedPinCodeContainerStyle); - } + if (focusedPinCodeContainerStyle && isFocusedContainer) { + stylesArray.push(focusedPinCodeContainerStyle); + } - if (filledPinCodeContainerStyle && Boolean(char)) { - stylesArray.push(filledPinCodeContainerStyle); - } + if (filledPinCodeContainerStyle && Boolean(char)) { + stylesArray.push(filledPinCodeContainerStyle); + } - if (disabledPinCodeContainerStyle && disabled) { - stylesArray.push(disabledPinCodeContainerStyle); - } + if (disabledPinCodeContainerStyle && disabled) { + stylesArray.push(disabledPinCodeContainerStyle); + } - return stylesArray; - }; + return stylesArray; + }; - const placeholderStyle = { - opacity: !!placeholder ? 0.5 : pinCodeTextStyle?.opacity || 1, - ...(!!placeholder ? placeholderTextStyle : []), - }; + const placeholderStyle = { + opacity: !!placeholder ? 0.5 : pinCodeTextStyle?.opacity || 1, + ...(!!placeholder ? placeholderTextStyle : []), + }; - return ( - - {Array(numberOfDigits) - .fill(0) - .map((_, index) => { - const isPlaceholderCell = !!placeholder && !text?.[index]; - const char = isPlaceholderCell ? placeholder?.[index] || " " : text[index]; - const isFocusedInput = index === focusedInputIndex && !disabled && Boolean(isFocused); - const isFilledLastInput = text.length === numberOfDigits && index === text.length - 1; - const isFocusedContainer = isFocusedInput || (isFilledLastInput && Boolean(isFocused)); + return ( + + {Array(numberOfDigits) + .fill(0) + .map((_, index) => { + const isPlaceholderCell = !!placeholder && !text?.[index]; + const char = isPlaceholderCell ? placeholder?.[index] || " " : text[index]; + const isFocusedInput = index === focusedInputIndex && !disabled && Boolean(isFocused); + const isFilledLastInput = text.length === numberOfDigits && index === text.length - 1; + const isFocusedContainer = isFocusedInput || (isFilledLastInput && Boolean(isFocused)); - return ( - - {isFocusedInput && !hideStick ? ( - - ) : ( - - {char && secureTextEntry ? "•" : char} - - )} - - ); - })} - - - ); -}); + return ( + + {isFocusedInput && !hideStick ? ( + + ) : ( + + {char && secureTextEntry ? "•" : char} + + )} + + ); + })} + + + ); + } +); diff --git a/src/OtpInput/OtpInput.types.ts b/src/OtpInput/OtpInput.types.ts index 6c50a3b..9ea888b 100644 --- a/src/OtpInput/OtpInput.types.ts +++ b/src/OtpInput/OtpInput.types.ts @@ -18,13 +18,13 @@ export interface OtpInputProps { textProps?: TextProps; type?: "alpha" | "numeric" | "alphanumeric"; placeholder?: string; + defaultValue?: string; } export interface OtpInputRef { clear: () => void; focus: () => void; setValue: (value: string) => void; - blur: () => void; } export interface Theme { diff --git a/src/OtpInput/useOtpInput.ts b/src/OtpInput/useOtpInput.ts index d8545b7..1db8caa 100644 --- a/src/OtpInput/useOtpInput.ts +++ b/src/OtpInput/useOtpInput.ts @@ -1,4 +1,4 @@ -import { useMemo, useRef, useState } from "react"; +import { useMemo, useRef, useState, useEffect } from "react"; import { Keyboard, TextInput } from "react-native"; import { OtpInputProps } from "./OtpInput.types"; @@ -19,18 +19,24 @@ export const useOtpInput = ({ onFocus, onBlur, placeholder: _placeholder, + defaultValue = "", // Default value support }: OtpInputProps) => { - const [text, setText] = useState(""); + const [text, setText] = useState(defaultValue.slice(0, numberOfDigits)); // Initialize with defaultValue const [isFocused, setIsFocused] = useState(autoFocus); const inputRef = useRef(null); const focusedInputIndex = text.length; + const placeholder = useMemo( () => (_placeholder?.length === 1 ? _placeholder.repeat(numberOfDigits) : _placeholder), [_placeholder, numberOfDigits] ); + useEffect(() => { + // Ensure state updates if defaultValue changes dynamically + setText(defaultValue.slice(0, numberOfDigits)); + }, [defaultValue, numberOfDigits]); + const handlePress = () => { - // To fix bug when keyboard is not popping up after being dismissed if (!Keyboard.isVisible()) { Keyboard.dismiss(); } @@ -40,17 +46,20 @@ export const useOtpInput = ({ const handleTextChange = (value: string) => { if (type && regexMap[type].test(value)) return; if (disabled) return; + setText(value); onTextChange?.(value); + if (value.length === numberOfDigits) { onFilled?.(value); - blurOnFilled && inputRef.current?.blur(); + if (blurOnFilled) inputRef.current?.blur(); } }; const setTextWithRef = (value: string) => { - const normalizedValue = value.length > numberOfDigits ? value.slice(0, numberOfDigits) : value; - handleTextChange(normalizedValue); + const normalizedValue = value.slice(0, numberOfDigits); + setText(normalizedValue); + onTextChange?.(normalizedValue); }; const clear = () => { @@ -61,10 +70,6 @@ export const useOtpInput = ({ inputRef.current?.focus(); }; - const blur = () => { - inputRef.current?.blur(); - }; - const handleFocus = () => { setIsFocused(true); onFocus?.(); @@ -77,7 +82,7 @@ export const useOtpInput = ({ return { models: { text, inputRef, focusedInputIndex, isFocused, placeholder }, - actions: { handlePress, handleTextChange, clear, focus, blur, handleFocus, handleBlur }, - forms: { setText, setTextWithRef }, + actions: { handlePress, handleTextChange, clear, focus, handleFocus, handleBlur }, + forms: { setTextWithRef }, }; };