<div id="root"></div>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
.base {
margin: 0;
padding: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: "Inter", sans-serif;
h1 {
font-size: 1.2rem;
}
.otp-base {
width: auto;
display: flex;
align-items: center;
justify-content: space-evenly;
gap: 0.5rem;
input {
width: 1ch;
padding: 1rem;
border-top:0px solid transparent;
border-left:0px solid transparent;
border-right:0px solid transparent;
border-bottom: 3px solid rgba(0,0,0,0.5);
outline: none;
font-size: 2rem;
transition:all .2s ease;
&:focus {
border-bottom: 3px solid #574b90;
}
}
}
.button{
visibility:hidden;
position:absolute;
margin-top:2rem;
right:0;
text-transform:uppercase;
padding:.4rem;
background:none;
border:1px solid #574b90;
border-radius:5px;
cursor:pointer;
transition:all .2s ease;
&.show{
visibility:visible;
}
&:hover{
background:#574b90;
color:#fff;
}
}
.otp-error {
animation: shake .3s ease forwards;
pointer-events:none;
input {
border-bottom: 2px solid red;
pointer-events:none;
&:focus{
border-bottom: 2px solid red;
}
}
}
}
// animations - shake
@keyframes shake {
0% {
transform: translateX(0);
}
25% {
transform: translateX(5px);
}
50% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
100% {
transform: translateX(0);
}
}
View Compiled
const { useState, useEffect, useRef } = React;
const App = () => {
const [otp, newOtp] = useState();
const [verfied, setVerfied] = useState(false);
const [otpVal, setOtpVal] = useState([]);
const textBase = useRef(null);
// generate random otp for each first render
useEffect(() => {
newOtp(Math.floor(1000 + Math.random() * 9000));
}, []);
const clearAll = () => {
textBase.current.classList.remove("otp-error");
textBase.current.childNodes.forEach((child) => {
child.value = "";
});
setOtpVal([]);
setVerfied(false);
};
const getOtp = () => {
if (parseInt(otpVal.join("")) === otp) {
textBase.current.classList.remove("otp-error");
setVerfied(true);
} else {
textBase.current.classList.add("otp-error");
}
};
const focusNext = (e) => {
const childCount = textBase.current.childElementCount;
const currentIndex = [...e.target.parentNode.children].indexOf(e.target);
if (currentIndex !== childCount - 1) {
e.target.nextSibling.focus();
} else {
const values = [];
textBase.current.childNodes.forEach((child) => {
values.push(child.value);
});
if (values.length !== 0) {
setOtpVal(values);
}
}
};
useEffect(() => {
if (otpVal.length === textBase.current.childElementCount) {
getOtp();
}
}, [otpVal]);
return (
<div className="base">
{!verfied ? (
<>
<h1> Enter OTP : {otp}</h1>
<div className="otp-base" ref={textBase}>
{new Array(4).fill(null).map((input) => {
return <input type="text" onChange={(e) => focusNext(e)} />;
})}
</div>
<button
className={`button ${otpVal.length > 0 && "show"}`}
onClick={() => clearAll()}
>
clear otp
</button>
</>
) : (
<> verified</>
)}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
View Compiled
This Pen doesn't use any external CSS resources.