Redesign the login form to not use the weird open fields
This commit is contained in:
parent
56640253b9
commit
7f3ab8aadf
6 changed files with 43 additions and 169 deletions
|
@ -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;
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
Loading…
Reference in a new issue