@kunlexze1/react-native-otp-timer
Version:
OTP input component + countdown timer hook for React Native (using react-native-otp-entry)
1 lines • 15.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/OtpWithTimer.tsx","../src/useTimer.ts","../src/index.ts"],"sourcesContent":["import React, { useEffect } from 'react';\r\nimport { View, Text, StyleSheet, TextStyle, ViewStyle, TouchableOpacity } from 'react-native';\r\nimport { OtpInput, OtpInputRef } from 'react-native-otp-entry';\r\nimport useTimer from './useTimer';\r\n\r\n// Timer-specific props\r\ninterface TimerProps {\r\n /** Duration of the timer in seconds */\r\n timerDuration?: number;\r\n /** Custom style for the timer text */\r\n timerStyle?: TextStyle;\r\n /** Custom formatter for the timer display */\r\n timerFormatter?: (timeLeft: string) => string;\r\n /** Whether to show the timer */\r\n showTimer?: boolean;\r\n /** Position of the timer relative to OTP input */\r\n timerPosition?: 'top' | 'bottom';\r\n /** Callback when timer expires */\r\n onTimerExpire?: () => void;\r\n /** Callback when timer starts */\r\n onTimerStart?: () => void;\r\n /** Callback when timer resets */\r\n onTimerReset?: () => void;\r\n /** Whether to auto-start the timer */\r\n autoStartTimer?: boolean;\r\n /** Callback to restart the timer manually */\r\n onRestartTimer?: () => void;\r\n /** Show restart button when timer expires */\r\n showRestartButton?: boolean;\r\n /** Custom text for restart button */\r\n restartButtonText?: string;\r\n /** Custom style for restart button */\r\n restartButtonStyle?: TextStyle;\r\n}\r\n\r\n// All props from react-native-otp-entry\r\ninterface OtpEntryProps {\r\n /** Number of digits for the OTP input */\r\n numberOfDigits: number;\r\n /** Focus color for the input */\r\n focusColor?: string;\r\n /** Auto focus on mount */\r\n autoFocus?: boolean;\r\n /** Hide the focus stick */\r\n hideStick?: boolean;\r\n /** Placeholder text */\r\n placeholder?: string;\r\n /** Blur on filled */\r\n blurOnFilled?: boolean;\r\n /** Disable the input */\r\n disabled?: boolean;\r\n /** Input type */\r\n type?: 'numeric' | 'alpha' | 'alphanumeric';\r\n /** Secure text entry */\r\n secureTextEntry?: boolean;\r\n /** Focus stick blinking duration */\r\n focusStickBlinkingDuration?: number;\r\n /** Callback when OTP is filled */\r\n onFilled?: (text: string) => void;\r\n /** Callback when text changes */\r\n onTextChange?: (text: string) => void;\r\n /** Text input props */\r\n textInputProps?: any;\r\n /** Text props */\r\n textProps?: any;\r\n /** Theme configuration */\r\n theme?: {\r\n containerStyle?: ViewStyle;\r\n inputsContainerStyle?: ViewStyle;\r\n pinCodeContainerStyle?: ViewStyle;\r\n pinCodeTextStyle?: TextStyle;\r\n focusStickStyle?: ViewStyle;\r\n focusedPinCodeContainerStyle?: ViewStyle;\r\n filledPinCodeContainerStyle?: ViewStyle;\r\n };\r\n}\r\n\r\n// Combined props interface\r\nexport interface OtpWithTimerProps extends OtpEntryProps, TimerProps {\r\n /** Container style for the entire component */\r\n containerStyle?: ViewStyle;\r\n /** Reference to the OTP input */\r\n ref?: React.Ref<OtpInputRef>;\r\n}\r\n\r\nconst OtpWithTimer = React.forwardRef<OtpInputRef, OtpWithTimerProps>(\r\n (\r\n {\r\n // Timer props\r\n timerDuration = 60,\r\n timerStyle,\r\n timerFormatter,\r\n showTimer = true,\r\n timerPosition = 'top',\r\n onTimerExpire,\r\n onTimerStart,\r\n onTimerReset,\r\n onRestartTimer,\r\n autoStartTimer = true,\r\n showRestartButton = true,\r\n restartButtonText = 'Resend OTP',\r\n restartButtonStyle,\r\n containerStyle,\r\n \r\n // OTP Entry props\r\n numberOfDigits,\r\n focusColor,\r\n autoFocus,\r\n hideStick,\r\n placeholder,\r\n blurOnFilled,\r\n disabled,\r\n type,\r\n secureTextEntry,\r\n focusStickBlinkingDuration,\r\n onFilled,\r\n onTextChange,\r\n textInputProps,\r\n textProps,\r\n theme,\r\n ...rest\r\n },\r\n ref\r\n ) => {\r\n const { timeLeft, hasTimeout, startTimer, resetOtpTimer } = useTimer(timerDuration);\r\n\r\n const handleRestartTimer = () => {\r\n resetOtpTimer();\r\n startTimer();\r\n onRestartTimer?.();\r\n onTimerStart?.();\r\n };\r\n\r\n useEffect(() => {\r\n if (autoStartTimer) {\r\n startTimer();\r\n onTimerStart?.();\r\n }\r\n }, [autoStartTimer, startTimer, onTimerStart]);\r\n\r\n useEffect(() => {\r\n if (hasTimeout) {\r\n onTimerExpire?.();\r\n }\r\n }, [hasTimeout, onTimerExpire]);\r\n\r\n const handleFilled = (text: string) => {\r\n onFilled?.(text);\r\n };\r\n\r\n const handleTextChange = (text: string) => {\r\n onTextChange?.(text);\r\n };\r\n\r\n const formatTimerDisplay = (time: string) => {\r\n return timerFormatter ? timerFormatter(time) : time;\r\n };\r\n\r\n const renderTimer = () => {\r\n if (!showTimer) return null;\r\n\r\n return (\r\n <Text style={[styles.timerText, timerStyle]}>\r\n {hasTimeout ? 'Timer expired' : formatTimerDisplay(timeLeft)}\r\n </Text>\r\n );\r\n };\r\n\r\n return (\r\n <View style={[styles.container, containerStyle]}>\r\n {timerPosition === 'top' && renderTimer()}\r\n \r\n <OtpInput\r\n ref={ref}\r\n numberOfDigits={numberOfDigits}\r\n focusColor={focusColor}\r\n autoFocus={autoFocus}\r\n hideStick={hideStick}\r\n placeholder={placeholder}\r\n blurOnFilled={blurOnFilled}\r\n disabled={disabled || hasTimeout}\r\n type={type}\r\n secureTextEntry={secureTextEntry}\r\n focusStickBlinkingDuration={focusStickBlinkingDuration}\r\n onFilled={handleFilled}\r\n onTextChange={handleTextChange}\r\n textInputProps={textInputProps}\r\n textProps={textProps}\r\n theme={theme}\r\n {...rest}\r\n />\r\n \r\n {timerPosition === 'bottom' && renderTimer()}\r\n \r\n {hasTimeout && (\r\n <View style={styles.expiredContainer}>\r\n <Text style={styles.expiredText}>\r\n Time expired. Please request a new OTP.\r\n </Text>\r\n {showRestartButton && (\r\n <TouchableOpacity onPress={handleRestartTimer}>\r\n <Text style={[styles.restartButton, restartButtonStyle]}>\r\n {restartButtonText}\r\n </Text>\r\n </TouchableOpacity>\r\n )}\r\n </View>\r\n )}\r\n </View>\r\n );\r\n }\r\n);\r\n\r\n// Expose the timer control methods\r\nexport const useOtpTimer = (timerDuration: number = 60) => {\r\n const timerHook = useTimer(timerDuration);\r\n \r\n const restartTimer = () => {\r\n timerHook.resetOtpTimer();\r\n timerHook.startTimer();\r\n };\r\n \r\n return {\r\n ...timerHook,\r\n resetTimer: timerHook.resetOtpTimer,\r\n restartTimer,\r\n };\r\n};\r\n\r\nconst styles = StyleSheet.create({\r\n container: {\r\n alignItems: 'center',\r\n },\r\n timerText: {\r\n fontSize: 16,\r\n color: '#333',\r\n marginVertical: 10,\r\n textAlign: 'center',\r\n },\r\n expiredContainer: {\r\n alignItems: 'center',\r\n marginTop: 10,\r\n },\r\n expiredText: {\r\n fontSize: 12,\r\n color: '#FF0000',\r\n textAlign: 'center',\r\n marginBottom: 8,\r\n },\r\n restartButton: {\r\n fontSize: 14,\r\n color: '#007AFF',\r\n fontWeight: '600',\r\n textDecorationLine: 'underline',\r\n paddingVertical: 8,\r\n paddingHorizontal: 16,\r\n },\r\n});\r\n\r\nOtpWithTimer.displayName = 'OtpWithTimer';\r\n\r\nexport default OtpWithTimer;\r\n","import { useState, useRef, useCallback, useEffect } from 'react';\r\nimport { AppState } from 'react-native';\r\n\r\nconst useTimer = (seconds: number) => {\r\n const [timeLeft, setTimeLeft] = useState(seconds);\r\n const [hasTimeout, setHasTimeout] = useState(false);\r\n const [isRunning, setIsRunning] = useState(false);\r\n\r\n const startTimeRef = useRef<number>(Date.now());\r\n const timerRef = useRef<NodeJS.Timeout | null>(null);\r\n\r\n const formatTime = useCallback((time: number): string => {\r\n const minutes = Math.floor(time / 60);\r\n const timeSeconds = time % 60;\r\n return `${minutes}mins : ${timeSeconds.toString().padStart(2, '0')}secs`;\r\n }, []);\r\n\r\n const updateTimer = useCallback(() => {\r\n const elapsedTime = Math.floor((Date.now() - startTimeRef.current) / 1000);\r\n const remainingTime = seconds - elapsedTime;\r\n\r\n if (remainingTime <= 0) {\r\n if (timerRef.current) {\r\n clearInterval(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n setHasTimeout(true);\r\n setIsRunning(false);\r\n setTimeLeft(0);\r\n } else {\r\n setTimeLeft(remainingTime);\r\n }\r\n }, [seconds]);\r\n\r\n const startTimer = useCallback(() => {\r\n if (timerRef.current) {\r\n clearInterval(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n\r\n setIsRunning(true);\r\n setHasTimeout(false);\r\n setTimeLeft(seconds);\r\n startTimeRef.current = Date.now();\r\n timerRef.current = setInterval(updateTimer, 1000);\r\n }, [seconds, updateTimer]);\r\n\r\n const resetOtpTimer = useCallback(() => {\r\n setHasTimeout(false);\r\n setIsRunning(false);\r\n setTimeLeft(seconds);\r\n startTimeRef.current = Date.now();\r\n\r\n if (timerRef.current) {\r\n clearInterval(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n }, [seconds]);\r\n\r\n const endTimer = useCallback(() => {\r\n if (timerRef.current) {\r\n clearInterval(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n setHasTimeout(true);\r\n setTimeLeft(0);\r\n setIsRunning(false);\r\n }, []);\r\n\r\n useEffect(() => {\r\n const handleAppStateChange = (nextAppState: string) => {\r\n if (nextAppState === 'active') {\r\n const elapsedTime = Math.floor((Date.now() - startTimeRef.current) / 1000);\r\n const remainingTime = seconds - elapsedTime;\r\n setTimeLeft(remainingTime > 0 ? remainingTime : 0);\r\n }\r\n };\r\n\r\n const subscription = AppState.addEventListener('change', handleAppStateChange);\r\n\r\n return () => {\r\n if (timerRef.current) {\r\n clearInterval(timerRef.current);\r\n timerRef.current = null;\r\n }\r\n subscription.remove();\r\n };\r\n }, [seconds]);\r\n\r\n return {\r\n timeLeft: formatTime(timeLeft),\r\n hasTimeout,\r\n isRunning,\r\n startTimer,\r\n resetOtpTimer,\r\n endTimer,\r\n };\r\n};\r\n\r\nexport default useTimer;\r\n","// Main component export\r\nexport { default as OtpWithTimer } from './OtpWithTimer';\r\nexport type { OtpWithTimerProps } from './OtpWithTimer';\r\n\r\n// Timer hook exports\r\nexport { default as useTimer } from './useTimer';\r\nexport { useOtpTimer } from './OtpWithTimer';\r\n\r\n// Re-export types and components from react-native-otp-entry for convenience\r\nexport { OtpInput } from 'react-native-otp-entry';\r\nexport type { OtpInputRef } from 'react-native-otp-entry';\r\n"],"mappings":"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","names":["useEffect","useEffect","OtpInput"]}