UNPKG

@siamf/otp-timer

Version:

A simple react otp timer component with resend functionality🤷

162 lines • 7.22 kB
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; import React from "react"; const isStyleObject = (obj) => typeof obj === "object" && obj !== null; const OtpInput = ({ value = "", numInputs = 4, onChange, onPaste, renderInput, shouldAutoFocus = false, inputType = "text", renderSeparator, placeholder, containerStyle, inputStyle, skipDefaultStyles = false, }) => { const [activeInput, setActiveInput] = React.useState(0); const inputRefs = React.useRef([]); const getOTPValue = () => (value ? value.toString().split("") : []); const isInputNum = inputType === "number" || inputType === "tel"; React.useEffect(() => { inputRefs.current = inputRefs.current.slice(0, numInputs); }, [numInputs]); React.useEffect(() => { var _a; if (shouldAutoFocus) { (_a = inputRefs.current[0]) === null || _a === void 0 ? void 0 : _a.focus(); } }, [shouldAutoFocus]); const getPlaceholderValue = () => { if (typeof placeholder === "string") { if (placeholder.length === numInputs) { return placeholder; } if (placeholder.length > 0) { console.error("Length of the placeholder should be equal to the number of inputs."); } } return undefined; }; const isInputValueValid = (value) => { const isTypeValid = isInputNum ? !isNaN(Number(value)) : typeof value === "string"; return isTypeValid && value.trim().length === 1; }; const handleChange = (event) => { const { value } = event.target; if (isInputValueValid(value)) { changeCodeAtFocus(value); focusInput(activeInput + 1); } }; const handleInputChange = (event) => { const { nativeEvent } = event; const value = event.target.value; if (!isInputValueValid(value)) { if (value.length === numInputs) { const hasInvalidInput = value.split("").some((cellInput) => !isInputValueValid(cellInput)); if (!hasInvalidInput) { handleOTPChange(value.split("")); focusInput(numInputs - 1); } } // @ts-expect-error - This was added previously to handle and edge case // for dealing with keyCode "229 Unidentified" on Android. Check if this is // still needed. if (nativeEvent.data === null && nativeEvent.inputType === "deleteContentBackward") { event.preventDefault(); changeCodeAtFocus(""); focusInput(activeInput - 1); } event.target.value = ""; } }; const handleFocus = (event) => (index) => { setActiveInput(index); event.target.select(); }; const handleBlur = () => { setActiveInput(activeInput - 1); }; const handleKeyDown = (event) => { const otp = getOTPValue(); if ([event.code, event.key].includes("Backspace")) { event.preventDefault(); changeCodeAtFocus(""); focusInput(activeInput - 1); } else if (event.code === "Delete") { event.preventDefault(); changeCodeAtFocus(""); } else if (event.code === "ArrowLeft") { event.preventDefault(); focusInput(activeInput - 1); } else if (event.code === "ArrowRight") { event.preventDefault(); focusInput(activeInput + 1); } else if (event.key === otp[activeInput]) { event.preventDefault(); focusInput(activeInput + 1); } else if (event.code === "Spacebar" || event.code === "Space" || event.code === "ArrowUp" || event.code === "ArrowDown") { event.preventDefault(); } }; const focusInput = (index) => { var _a, _b; const activeInput = Math.max(Math.min(numInputs - 1, index), 0); if (inputRefs.current[activeInput]) { (_a = inputRefs.current[activeInput]) === null || _a === void 0 ? void 0 : _a.focus(); (_b = inputRefs.current[activeInput]) === null || _b === void 0 ? void 0 : _b.select(); setActiveInput(activeInput); } }; const changeCodeAtFocus = (value) => { const otp = getOTPValue(); otp[activeInput] = value[0]; handleOTPChange(otp); }; const handleOTPChange = (otp) => { const otpValue = otp.join(""); onChange(otpValue); }; const handlePaste = (event) => { var _a; event.preventDefault(); const otp = getOTPValue(); let nextActiveInput = activeInput; const pastedData = event.clipboardData .getData("text/plain") .slice(0, numInputs - activeInput) .split(""); if (isInputNum && pastedData.some((value) => isNaN(Number(value)))) { return; } for (let pos = 0; pos < numInputs; ++pos) { if (pos >= activeInput && pastedData.length > 0) { otp[pos] = (_a = pastedData.shift()) !== null && _a !== void 0 ? _a : ""; nextActiveInput++; } } focusInput(nextActiveInput); handleOTPChange(otp); }; return (_jsx("div", { style: Object.assign({ display: "flex", alignItems: "center" }, isStyleObject(containerStyle) && containerStyle), className: typeof containerStyle === "string" ? containerStyle : undefined, onPaste: onPaste, children: Array.from({ length: numInputs }, (_, index) => index).map((index) => { var _a, _b, _c; return (_jsxs(React.Fragment, { children: [renderInput({ value: (_a = getOTPValue()[index]) !== null && _a !== void 0 ? _a : "", placeholder: (_c = (_b = getPlaceholderValue()) === null || _b === void 0 ? void 0 : _b[index]) !== null && _c !== void 0 ? _c : undefined, ref: (element) => { inputRefs.current[index] = element; }, onChange: handleChange, onFocus: (event) => handleFocus(event)(index), onBlur: handleBlur, onKeyDown: handleKeyDown, onPaste: handlePaste, autoComplete: "off", "aria-label": `Please enter OTP character ${index + 1}`, style: Object.assign(!skipDefaultStyles ? { width: "1em", textAlign: "center" } : {}, isStyleObject(inputStyle) ? inputStyle : {}), className: typeof inputStyle === "string" ? inputStyle : undefined, type: inputType, inputMode: isInputNum ? "numeric" : "text", onInput: handleInputChange, }, index), index < numInputs - 1 && (typeof renderSeparator === "function" ? renderSeparator(index) : renderSeparator)] }, index)); }) })); }; export default OtpInput; //# sourceMappingURL=OtpInput.js.map