Redesign the login form to not use the weird open fields

This commit is contained in:
Dane Everitt 2019-06-22 13:11:27 -07:00
parent 56640253b9
commit 7f3ab8aadf
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
6 changed files with 43 additions and 169 deletions

View file

@ -12,43 +12,10 @@ input[type=number] {
-moz-appearance: textfield !important;
}
/**
* Styles for the login form open input boxes. Label floats up above it when content
* is input and then sinks back down into the field if left empty.
*/
.input-open {
@apply .w-full .relative;
}
.input-open > .input, .input-open > .input:disabled {
@apply .appearance-none .block .w-full .text-neutral-800 .border-b-2 .border-neutral-200 .py-3 .px-2 .bg-white;
&:focus {
@apply .border-primary-400;
outline: 0;
transition: border 500ms ease-out;
}
&:focus + label, &:valid + label, &.has-content + label {
@apply .text-neutral-800 .px-0 .cursor-pointer;
transform:translateY(-26px)
}
&:required {
box-shadow: none;
}
}
.input-open > label {
@apply .block .uppercase .tracking-wide .text-neutral-500 .text-xs .mb-2 .px-2 .absolute;
top: 14px;
transition: padding 200ms linear, transform 200ms ease-out;
}
/**
* Styling for other forms throughout the Panel.
*/
.input:not(.open-label) {
.input {
@apply .appearance-none .p-3 .rounded .border .border-neutral-200 .text-neutral-800 .w-full;
min-width: 0;
transition: border 150ms linear;
@ -70,6 +37,10 @@ input[type=number] {
@apply .bg-neutral-100 .border-neutral-200;
}
label {
@apply .block .text-xs .font-medium .uppercase .text-neutral-700 .mb-2;
}
select:not(.appearance-none) {
@apply .outline-none .appearance-none .block .bg-white .border .border-neutral-200 .text-neutral-400 .p-3 .pr-8 rounded;
transition: border-color 150ms linear, color 150ms linear;

View file

@ -63,18 +63,19 @@ class ForgotPasswordContainer extends React.PureComponent<Props, State> {
Request Password Reset
</h2>
<form className={'login-box'} onSubmit={this.handleSubmission}>
<div className={'mt-3'}>
<OpenInputField
<label htmlFor={'email'}>Email</label>
<input
ref={this.emailField}
id={'email'}
type={'email'}
label={'Email'}
description={'Enter your account email address to receive instructions on resetting your password.'}
autoFocus={true}
required={true}
className={'input'}
onChange={this.handleFieldUpdate}
autoFocus={true}
/>
</div>
<p className={'input-help'}>
Enter your account email address to receive instructions on resetting your password.
</p>
<div className={'mt-6'}>
<button
className={'btn btn-primary btn-jumbo flex justify-center'}

View file

@ -16,41 +16,6 @@ class LoginCheckpointContainer extends React.PureComponent<RouteComponentProps,
isLoading: false,
};
moveToNextInput (e: React.KeyboardEvent<HTMLInputElement>, isBackspace: boolean = false) {
const form = e.currentTarget.form;
if (form) {
const index = Array.prototype.indexOf.call(form, e.currentTarget);
const element = form.elements[index + (isBackspace ? -1 : 1)];
// @ts-ignore
element && element.focus();
}
}
handleNumberInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
const number = Number(e.key);
if (isNaN(number)) {
return;
}
this.setState(s => ({ code: s.code + number.toString() }));
this.moveToNextInput(e);
};
handleBackspace = (e: React.KeyboardEvent<HTMLInputElement>) => {
const isBackspace = e.key === 'Delete' || e.key === 'Backspace';
if (!isBackspace || e.currentTarget.value.length > 0) {
e.currentTarget.value = '';
return;
}
this.setState(s => ({ code: s.code.substring(0, s.code.length - 2) }));
e.currentTarget.value = '';
this.moveToNextInput(e, true);
};
render () {
return (
<React.Fragment>
@ -64,19 +29,6 @@ class LoginCheckpointContainer extends React.PureComponent<RouteComponentProps,
code from your device in order to continue.
</p>
<div className={'flex mt-6'}>
{
[1, 2, 3, 4, 5, 6].map((_, index) => (
<input
autoFocus={index === 0}
key={`input_${index}`}
type={'number'}
onKeyPress={this.handleNumberInput}
onKeyDown={this.handleBackspace}
maxLength={1}
className={`input block flex-1 text-center text-lg ${index === 5 ? undefined : 'mr-6'}`}
/>
))
}
</div>
<div className={'mt-6'}>
<button

View file

@ -1,5 +1,4 @@
import * as React from 'react';
import OpenInputField from '@/components/forms/OpenInputField';
import { Link, RouteComponentProps } from 'react-router-dom';
import login from '@/api/auth/login';
import { httpErrorToHuman } from '@/api/http';
@ -65,23 +64,22 @@ export default class LoginContainer extends React.PureComponent<RouteComponentPr
</h2>
<NetworkErrorMessage message={this.state.errorMessage}/>
<form className={'login-box'} onSubmit={this.submit}>
<div className={'mt-3'}>
<OpenInputField
autoFocus={true}
label={'Username or Email'}
type={'text'}
required={true}
<label htmlFor={'username'}>Username or Email</label>
<input
id={'username'}
autoFocus={true}
required={true}
className={'input'}
onChange={this.handleFieldUpdate}
disabled={this.state.isLoading}
/>
</div>
<div className={'mt-6'}>
<OpenInputField
label={'Password'}
type={'password'}
required={true}
<label htmlFor={'password'}>Password</label>
<input
id={'password'}
required={true}
type={'password'}
className={'input'}
onChange={this.handleFieldUpdate}
disabled={this.state.isLoading}
/>

View file

@ -1,5 +1,4 @@
import * as React from 'react';
import OpenInputField from '@/components/forms/OpenInputField';
import { RouteComponentProps } from 'react-router';
import { parse } from 'query-string';
import { Link } from 'react-router-dom';
@ -93,30 +92,26 @@ class ResetPasswordContainer extends React.PureComponent<Props, State> {
</h2>
<NetworkErrorMessage message={this.state.errorMessage}/>
<form className={'login-box'} onSubmit={this.onSubmit}>
<div className={'mt-3'}>
<OpenInputField
label={'Email'}
value={this.state.email || ''}
disabled
/>
</div>
<label>Email</label>
<input value={this.state.email || ''} disabled={true}/>
<div className={'mt-6'}>
<OpenInputField
autoFocus={true}
label={'New Password'}
description={'Passwords must be at least 8 characters in length.'}
<label htmlFor={'new-password'}>New Password</label>
<input
id={'new-password'}
type={'password'}
required={true}
id={'password'}
onChange={this.onPasswordChange}
/>
<p className={'input-help'}>
Passwords must be at least 8 characters in length.
</p>
</div>
<div className={'mt-6'}>
<OpenInputField
label={'Confirm New Password'}
<label htmlFor={'new-password-confirm'}>Confirm New Password</label>
<input
id={'new-password-confirm'}
type={'password'}
required={true}
id={'password-confirm'}
onChange={this.onPasswordConfirmChange}
/>
</div>

View file

@ -1,43 +0,0 @@
import * as React from 'react';
import classNames from 'classnames';
type Props = React.InputHTMLAttributes<HTMLInputElement> & {
label: string;
description?: string;
value?: string;
};
export default React.forwardRef<HTMLInputElement, Props>(({ className, description, onChange, label, value, ...props }, ref) => {
const [ stateValue, setStateValue ] = React.useState(value);
if (value !== stateValue) {
setStateValue(value);
}
const classes = classNames('input open-label', {
'has-content': stateValue && stateValue.length > 0,
});
return (
<div className={'input-open'}>
<input
ref={ref}
className={classes}
onChange={e => {
setStateValue(e.target.value);
if (onChange) {
onChange(e);
}
}}
value={typeof value !== 'undefined' ? (stateValue || '') : undefined}
{...props}
/>
<label htmlFor={props.id}>{label}</label>
{description &&
<p className={'mt-2 text-xs text-neutral-500'}>
{description}
</p>
}
</div>
);
});