Buliding HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Controls</title>
<link rel="stylesheet" href="style.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0"
/>
</head>
<body>
<div class="textbox">
<input
oninput="handleChange(event)"
onkeydown="handleStartTyping()"
autocomplete="off"
id="input"
type="text"
/>
<span class="icon visible material-symbols-outlined">account_circle</span>
<label htmlFor="input">Username</label>
<span class="spinner"></span>
</div>
<button disabled>
<p>Sign up</p>
<span class="material-symbols-outlined"> arrow_right_alt </span>
</button>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
Styling CSS
body {
margin: 0;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: #151515;
font-family: "Euclid Circular A", "Poppins";
}
* {
box-sizing: border-box;
}
.textbox {
position: relative;
margin-bottom: 16px;
}
.textbox :is(label, span) {
position: absolute;
top: 50%;
translate: 0 -50%;
pointer-events: none;
color: #888888;
transition: 0.3s;
}
.textbox > label {
left: 44px;
translate: 0 -50%;
padding: 4px 8px;
}
.textbox > .icon {
z-index: 2;
left: 16px;
font-size: 26px;
}
.textbox > input {
height: 56px;
width: 240px;
padding-left: 48px;
border: 2px solid #292929;
border-radius: 8px;
outline: none;
background: transparent;
color: #f9f9f9;
font-family: inherit;
font-size: 16px;
transition: 0.3s;
}
.textbox > input.valid.has-value {
border-color: #14ca99;
}
.textbox > :is(input:focus, .has-value) {
border-color: #d3d3d3;
}
.textbox > input.has-value {
border-color: #ff5360;
}
.textbox > :is(input:focus, .has-value) ~ span {
color: #f9f9f9;
}
.textbox > :is(input:focus, .has-value) ~ label {
background: #151515;
color: rgba(255, 255, 255, 0.75);
translate: -42px -42px;
scale: 0.8;
}
@keyframes spin {
100% {
rotate: 1turn;
}
}
.spinner {
position: absolute;
top: 50%;
right: 16px;
translate: 0 -50%;
width: 24px;
height: 24px;
border-radius: 50%;
border: 3px solid rgb(255 255 255 / 14%);
border-top-color: #f7f7f7;
opacity: 0;
animation: spin 1s infinite linear;
}
.spinner.visible {
opacity: 1;
}
button {
width: 240px;
height: 56px;
border-radius: 6px;
border: 0;
font-family: inherit;
font-size: 16px;
display: flex;
align-items: center;
padding: 0 18px;
justify-content: space-between;
background: #2e3231;
color: #f7f7f7;
transition: 0.3s;
}
button:disabled {
opacity: 0.33;
}
JavaScript
const usernames = ["david", "david1", "david2"];
const input = document.querySelector("#input"),
spinner = document.querySelector(".spinner"),
button = document.querySelector("button");
const updateUi = (value) => {
console.log("value", value);
spinner.classList.remove("visible");
const usernameExists = usernames.some((u) => u === value);
console.log("usernames", usernames);
console.log("usernameExists", usernameExists);
const invalid = usernameExists || !value;
button.disabled = invalid;
input.classList.toggle("valid", !invalid);
};
const debounce = (callback, time) => {
let interval;
return (...args) => {
clearTimeout(interval);
interval = setTimeout(() => {
callback.apply(null, args);
}, time);
};
};
const handleStartTyping = () => {
spinner.classList.add("visible");
};
const handleChange = debounce((input) => {
const { value } = input.target;
console.log("input", input);
input.target.classList.toggle("has-value", value);
updateUi(value);
}, 500);

0 Comments