created input indicator class to be used in many places

This commit is contained in:
Miodec 2022-05-23 22:04:06 +02:00
parent b029d86460
commit 48fffe1b99
7 changed files with 164 additions and 144 deletions

View file

@ -391,3 +391,37 @@ key {
color: var(--bg-color);
}
}
.inputAndIndicator {
input {
width: 100%;
}
position: relative;
.statusIndicator {
width: 2.25rem;
height: 2.25rem;
position: absolute;
right: 0;
top: 0;
/* background: red; */
display: grid;
grid-template-columns: 2.25rem;
grid-template-rows: 2.25rem;
place-items: center center;
cursor: pointer;
.indicator {
grid-column: 1/2;
grid-row: 1/2;
&.level-1 {
color: var(--error-color);
}
&.level0 {
color: var(--sub-color);
}
&.level1 {
color: var(--main-color);
}
}
}
}

View file

@ -34,45 +34,6 @@
grid-area: form;
}
}
&.register {
.inputAndIndicator {
input {
width: 100%;
}
position: relative;
.checkStatus {
width: 2.25rem;
height: 2.25rem;
position: absolute;
right: 0;
top: 0;
/* background: red; */
display: grid;
grid-template-columns: 2.25rem;
grid-template-rows: 2.25rem;
place-items: center center;
cursor: pointer;
.checking,
.available,
.unavailable,
.taken {
grid-column: 1/2;
grid-row: 1/2;
}
.checking {
color: var(--sub-color);
}
.available {
color: var(--main-color);
}
.unavailable,
.taken {
color: var(--error-color);
}
}
}
}
}
form {

View file

@ -0,0 +1,71 @@
interface InputIndicatorOption {
icon: string;
spinIcon?: true;
message?: string;
level: -1 | 0 | 1;
}
export class InputIndicator {
private parentElement: JQuery<HTMLElement>;
private options: Record<string, InputIndicatorOption>;
constructor(
parentElement: JQuery<HTMLElement>,
options: Record<string, InputIndicatorOption>
) {
if (!parentElement.hasClass("inputAndIndicator")) {
throw new Error("Parent element must have class 'inputAndIndicator'");
}
this.parentElement = parentElement;
this.options = options;
let indicator = `<div class="statusIndicator">`;
for (const [optionId, option] of Object.entries(options)) {
indicator += `
<div
class="indicator level${option.level} hidden"
data-option-id="${optionId}"
${
//todo: find a good value for this
(option.message?.length ?? 0) > 20
? `data-balloon-length="large"`
: ""
}
data-balloon-pos="up"
${option.message ? `aria-label="${option.message}"` : ""}
>
<i class="fas fa-fw ${option.icon} ${
option.spinIcon ? "fa-spin" : ""
}"></i>
</div>
`;
}
indicator += `</div>`;
parentElement.append(indicator);
}
hide(): void {
this.parentElement.find(".statusIndicator div").addClass("hidden");
}
show(optionId: keyof typeof this.options, messageOverride?: string): void {
this.hide();
const indicator = this.parentElement.find(`[data-option-id="${optionId}"]`);
indicator.removeClass("hidden");
if (messageOverride) {
if (messageOverride.length > 20) {
indicator.attr("data-balloon-length", "large");
} else {
indicator.removeAttr("data-balloon-length");
}
indicator.attr("aria-label", messageOverride);
}
}
}

View file

@ -2,6 +2,7 @@ import { debounce } from "throttle-debounce";
import Ape from "../ape";
import Page from "./page";
import * as Notifications from "../elements/notifications";
import { InputIndicator } from "../elements/input-indicator";
export function enableInputs(): void {
$(".pageLogin .button").removeClass("disabled");
@ -21,34 +22,28 @@ export function hidePreloader(): void {
$(".pageLogin .preloader").addClass("hidden");
}
function updateIndicator(
state: "checking" | "available" | "unavailable" | "taken" | "none",
balloon?: string
): void {
$(".page.pageLogin .register.side .checkStatus .checking").addClass("hidden");
$(".page.pageLogin .register.side .checkStatus .available").addClass(
"hidden"
);
$(".page.pageLogin .register.side .checkStatus .unavailable").addClass(
"hidden"
);
$(".page.pageLogin .register.side .checkStatus .taken").addClass("hidden");
if (state !== "none") {
$(".page.pageLogin .register.side .checkStatus ." + state).removeClass(
"hidden"
);
if (balloon) {
$(".page.pageLogin .register.side .checkStatus ." + state).attr(
"aria-label",
balloon
);
} else {
$(".page.pageLogin .register.side .checkStatus ." + state).removeAttr(
"aria-label"
);
}
const nameIndicator = new InputIndicator(
$(".page.pageLogin .register.side .username.inputAndIndicator"),
{
available: {
icon: "fa-check",
level: 1,
},
unavailable: {
icon: "fa-times",
level: -1,
},
taken: {
icon: "fa-times",
level: -1,
},
checking: {
icon: "fa-circle-notch",
spinIcon: true,
level: 0,
},
}
}
);
const checkNameDebounced = debounce(1000, async () => {
const val = $(
@ -58,22 +53,22 @@ const checkNameDebounced = debounce(1000, async () => {
const response = await Ape.users.getNameAvailability(val);
if (response.status === 200) {
updateIndicator("available", response.message);
nameIndicator.show("available", response.message);
return;
}
if (response.status == 422) {
updateIndicator("unavailable", response.message);
nameIndicator.show("unavailable", response.message);
return;
}
if (response.status == 409) {
updateIndicator("taken", response.message);
nameIndicator.show("taken", response.message);
return;
}
if (response.status !== 200) {
updateIndicator("unavailable");
nameIndicator.show("unavailable", response.message);
return Notifications.add(
"Failed to check name availability: " + response.message,
-1
@ -87,9 +82,9 @@ $(".page.pageLogin .register.side .usernameInput").on("input", () => {
".page.pageLogin .register.side .usernameInput"
).val() as string;
if (val === "") {
return updateIndicator("none");
return nameIndicator.hide();
} else {
updateIndicator("checking");
nameIndicator.show("checking");
checkNameDebounced();
}
}, 1);

View file

@ -15,6 +15,7 @@ import * as TestLogic from "../test/test-logic";
import * as DB from "../db";
import * as Loader from "../elements/loader";
import { subscribe as subscribeToSignUpEvent } from "../observables/google-sign-up-event";
import { InputIndicator } from "../elements/input-indicator";
let signedInUser: UserCredential | undefined = undefined;
@ -130,27 +131,6 @@ async function apply(): Promise<void> {
}
}
function updateIndicator(
state: "checking" | "available" | "unavailable" | "taken" | "none",
balloon?: string
): void {
$("#googleSignUpPopup .checkStatus .checking").addClass("hidden");
$("#googleSignUpPopup .checkStatus .available").addClass("hidden");
$("#googleSignUpPopup .checkStatus .unavailable").addClass("hidden");
$("#googleSignUpPopup .checkStatus .taken").addClass("hidden");
if (state !== "none") {
$("#googleSignUpPopup .checkStatus ." + state).removeClass("hidden");
if (balloon) {
$("#googleSignUpPopup .checkStatus ." + state).attr(
"aria-label",
balloon
);
} else {
$("#googleSignUpPopup .checkStatus ." + state).removeAttr("aria-label");
}
}
}
function enableButton(): void {
$("#googleSignUpPopup .button").removeClass("disabled");
}
@ -173,29 +153,52 @@ $("#googleSignUpPopupWrapper").on("mousedown", (e) => {
}
});
const nameIndicator = new InputIndicator(
$("#googleSignUpPopup .inputAndIndicator"),
{
available: {
icon: "fa-check",
level: 1,
},
unavailable: {
icon: "fa-times",
level: -1,
},
taken: {
icon: "fa-times",
level: -1,
},
checking: {
icon: "fa-circle-notch",
spinIcon: true,
level: 0,
},
}
);
const checkNameDebounced = debounce(1000, async () => {
const val = $("#googleSignUpPopup input").val() as string;
if (!val) return;
const response = await Ape.users.getNameAvailability(val);
if (response.status === 200) {
updateIndicator("available", response.message);
nameIndicator.show("available", response.message);
enableButton();
return;
}
if (response.status == 422) {
updateIndicator("unavailable", response.message);
nameIndicator.show("unavailable", response.message);
return;
}
if (response.status == 409) {
updateIndicator("taken", response.message);
nameIndicator.show("taken", response.message);
return;
}
if (response.status !== 200) {
updateIndicator("unavailable");
nameIndicator.show("unavailable");
return Notifications.add(
"Failed to check name availability: " + response.message,
-1
@ -208,9 +211,9 @@ $("#googleSignUpPopup input").on("input", () => {
disableButton();
const val = $("#googleSignUpPopup input").val() as string;
if (val === "") {
return updateIndicator("none");
return nameIndicator.hide();
} else {
updateIndicator("checking");
nameIndicator.show("checking");
checkNameDebounced();
}
}, 1);

View file

@ -5,35 +5,13 @@
<div class="register side">
<div class="title">register</div>
<form action="" autocomplete="nope">
<div class="inputAndIndicator">
<div class="inputAndIndicator username">
<input
type="text"
class="usernameInput"
placeholder="username"
autocomplete="new-username"
/>
<div class="checkStatus">
<div
class="checking hidden"
data-balloon-length="large"
data-balloon-pos="up"
>
<i class="fas fa-fw fa-circle-notch fa-spin"></i>
</div>
<div class="available hidden" data-balloon-pos="up">
<i class="fas fa-fw fa-check"></i>
</div>
<div
class="unavailable hidden"
data-balloon-length="large"
data-balloon-pos="up"
>
<i class="fas fa-fw fa-times"></i>
</div>
<div class="taken hidden" data-balloon-pos="up">
<i class="fas fa-fw fa-user-friends"></i>
</div>
</div>
</div>
<input type="email" placeholder="email" autocomplete="new-email" />
<input

View file

@ -476,28 +476,6 @@
<div class="text">You need to choose a username before continuing</div>
<div class="inputAndIndicator">
<input type="text" placeholder="username" />
<div class="checkStatus">
<div
class="checking hidden"
data-balloon-length="large"
data-balloon-pos="up"
>
<i class="fas fa-fw fa-circle-notch fa-spin"></i>
</div>
<div class="available hidden" data-balloon-pos="up">
<i class="fas fa-fw fa-check"></i>
</div>
<div
class="unavailable hidden"
data-balloon-length="large"
data-balloon-pos="up"
>
<i class="fas fa-fw fa-times"></i>
</div>
<div class="taken hidden" data-balloon-pos="up">
<i class="fas fa-fw fa-user-friends"></i>
</div>
</div>
</div>
<div class="button disabled">Sign up</div>
</div>