mirror of
https://github.com/nestriness/nestri.git
synced 2026-11-03 14:12:39 +02:00
style(auth): ring a refused or wrong answer red while it shakes
A code field silently dropped a character it does not accept, so typing a letter into the emailed code, which is digits only, looked exactly like a field that does not work. It now shakes the boxes and rings them red, the same answer a wrong code gets. Dashes and spaces are still taken quietly, since they are how a code is written. The shake an error page plays on load now rings the field red too, fading back to its usual border once it settles, on every field including the email address. Reduced motion keeps the red and drops the movement, since the red is the message.
This commit is contained in:
@@ -565,9 +565,10 @@ form:has([data-component='input']:invalid) [data-component='button']:hover {
|
||||
}
|
||||
|
||||
|
||||
/* A wrong answer shakes the field it came from, once. Every error is a new
|
||||
page, so an animation that runs on load runs exactly when one arrives —
|
||||
no script needed to trigger it. */
|
||||
/* A wrong answer shakes the field it came from, once, and rings it red while
|
||||
it does. Every error is a new page, so an animation that runs on load runs
|
||||
exactly when one arrives — no script needed to trigger it. \`segments.ts\`
|
||||
plays the same two when a code field refuses a character. */
|
||||
@keyframes shake {
|
||||
0%, 100% { translate: 0; }
|
||||
15%, 55% { translate: -0.5rem; }
|
||||
@@ -575,8 +576,18 @@ form:has([data-component='input']:invalid) [data-component='button']:hover {
|
||||
90% { translate: -0.2rem; }
|
||||
}
|
||||
|
||||
/* No end frame, so it fades back to whatever border the field has now. */
|
||||
@keyframes alarm {
|
||||
0%, 60% {
|
||||
border-color: var(--color-red-600);
|
||||
box-shadow: 0 0 0 1px var(--color-red-600);
|
||||
}
|
||||
}
|
||||
|
||||
form:has([data-component='form-alert'][data-color='danger']) [data-component='input'] {
|
||||
animation: shake 400ms ease-in-out;
|
||||
animation:
|
||||
shake 400ms ease-in-out,
|
||||
alarm 1s ease-out;
|
||||
}
|
||||
|
||||
[data-component='input'][data-variant='code']::placeholder {
|
||||
@@ -615,7 +626,11 @@ form:has([data-component='form-alert'][data-color='danger']) [data-component='in
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
form:has([data-component='form-alert'][data-color='danger']) [data-component='input'],
|
||||
/* The red stays: it is the message. Only the movement goes. */
|
||||
form:has([data-component='form-alert'][data-color='danger']) [data-component='input'] {
|
||||
animation: alarm 1s ease-out;
|
||||
}
|
||||
|
||||
[data-component='input'][data-variant='box'][data-active]:empty::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,34 @@ export default `(() => {
|
||||
if (real.selectionStart !== end || real.selectionEnd !== end) real.setSelectionRange(end, end);
|
||||
};
|
||||
|
||||
// A refused character gets the same answer as a wrong code: the boxes
|
||||
// shake and ring red. Silently dropping it looked exactly like a field
|
||||
// that does not work. Played through script rather than a class, because
|
||||
// the error page may already be running the stylesheet's own shake on
|
||||
// these boxes, and toggling a class cannot restart an animation that is
|
||||
// still named the same.
|
||||
const refuse = () => {
|
||||
const red = getComputedStyle(root).getPropertyValue('--color-red-600').trim() || 'red';
|
||||
const ring = { borderColor: red, boxShadow: '0 0 0 1px ' + red };
|
||||
const still = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
boxes.forEach((box) => {
|
||||
box.animate([ring, Object.assign({ offset: 0.6 }, ring)], { duration: 1000, easing: 'ease-out' });
|
||||
if (!still)
|
||||
box.animate(
|
||||
[
|
||||
{ translate: '0' },
|
||||
{ translate: '-0.5rem', offset: 0.15 },
|
||||
{ translate: '0.5rem', offset: 0.35 },
|
||||
{ translate: '-0.5rem', offset: 0.55 },
|
||||
{ translate: '0.5rem', offset: 0.75 },
|
||||
{ translate: '-0.2rem', offset: 0.9 },
|
||||
{ translate: '0' }
|
||||
],
|
||||
{ duration: 400, easing: 'ease-in-out' }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const value = real.value;
|
||||
const focused = document.activeElement === real;
|
||||
@@ -75,6 +103,11 @@ export default `(() => {
|
||||
};
|
||||
|
||||
real.addEventListener('input', () => {
|
||||
// Separators and spaces are part of how a code is written, not a
|
||||
// mistake; anything else that \`clean\` drops is.
|
||||
const written = real.value.replace(/[\\s-]/g, '');
|
||||
const allowed = numeric ? /^[0-9]*$/ : /^[0-9a-zA-Z]*$/;
|
||||
if (!allowed.test(written)) refuse();
|
||||
const cleaned = clean(real.value);
|
||||
if (cleaned !== real.value) real.value = cleaned;
|
||||
toEnd();
|
||||
|
||||
Reference in New Issue
Block a user