feat(login): add spinner animation during authentication
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+10
-3
@@ -54,7 +54,7 @@ export async function render(container) {
|
|||||||
<div class="login-error" id="login-error" role="alert" aria-live="polite" hidden></div>
|
<div class="login-error" id="login-error" role="alert" aria-live="polite" hidden></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn--primary login-form__submit" id="login-btn">
|
<button type="submit" class="btn btn--primary login-form__submit" id="login-btn">
|
||||||
${t('login.loginButton')}
|
<span class="login-btn__label">${t('login.loginButton')}</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,8 +84,14 @@ export async function render(container) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const labelEl = submitBtn.querySelector('.login-btn__label');
|
||||||
|
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
submitBtn.textContent = t('login.loggingIn');
|
labelEl.textContent = t('login.loggingIn');
|
||||||
|
const spinner = document.createElement('span');
|
||||||
|
spinner.className = 'login-spinner';
|
||||||
|
spinner.setAttribute('aria-hidden', 'true');
|
||||||
|
submitBtn.insertBefore(spinner, labelEl);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await auth.login(username, password);
|
const result = await auth.login(username, password);
|
||||||
@@ -97,7 +103,8 @@ export async function render(container) {
|
|||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
submitBtn.textContent = t('login.loginButton');
|
labelEl.textContent = t('login.loginButton');
|
||||||
|
spinner.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,10 @@
|
|||||||
.login-form__submit {
|
.login-form__submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: var(--space-2);
|
margin-top: var(--space-2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-error {
|
.login-error {
|
||||||
@@ -63,3 +67,21 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-spinner {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
|
||||||
|
border-top-color: currentColor;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: login-spin 0.7s linear infinite;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes login-spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.login-spinner { animation: none; opacity: 0.6; }
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user