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