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;
|
-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.
|
* 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;
|
@apply .appearance-none .p-3 .rounded .border .border-neutral-200 .text-neutral-800 .w-full;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
transition: border 150ms linear;
|
transition: border 150ms linear;
|
||||||
|
@ -70,6 +37,10 @@ input[type=number] {
|
||||||
@apply .bg-neutral-100 .border-neutral-200;
|
@apply .bg-neutral-100 .border-neutral-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
@apply .block .text-xs .font-medium .uppercase .text-neutral-700 .mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
select:not(.appearance-none) {
|
select:not(.appearance-none) {
|
||||||
@apply .outline-none .appearance-none .block .bg-white .border .border-neutral-200 .text-neutral-400 .p-3 .pr-8 rounded;
|
@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;
|
transition: border-color 150ms linear, color 150ms linear;
|
||||||
|
|
|
@ -63,18 +63,19 @@ class ForgotPasswordContainer extends React.PureComponent<Props, State> {
|
||||||
Request Password Reset
|
Request Password Reset
|
||||||
</h2>
|
</h2>
|
||||||
<form className={'login-box'} onSubmit={this.handleSubmission}>
|
<form className={'login-box'} onSubmit={this.handleSubmission}>
|
||||||
<div className={'mt-3'}>
|
<label htmlFor={'email'}>Email</label>
|
||||||
<OpenInputField
|
<input
|
||||||
ref={this.emailField}
|
ref={this.emailField}
|
||||||
id={'email'}
|
id={'email'}
|
||||||
type={'email'}
|
type={'email'}
|
||||||
label={'Email'}
|
required={true}
|
||||||
description={'Enter your account email address to receive instructions on resetting your password.'}
|
className={'input'}
|
||||||
autoFocus={true}
|
onChange={this.handleFieldUpdate}
|
||||||
required={true}
|
autoFocus={true}
|
||||||
onChange={this.handleFieldUpdate}
|
/>
|
||||||
/>
|
<p className={'input-help'}>
|
||||||
</div>
|
Enter your account email address to receive instructions on resetting your password.
|
||||||
|
</p>
|
||||||
<div className={'mt-6'}>
|
<div className={'mt-6'}>
|
||||||
<button
|
<button
|
||||||
className={'btn btn-primary btn-jumbo flex justify-center'}
|
className={'btn btn-primary btn-jumbo flex justify-center'}
|
||||||
|
|
|
@ -16,41 +16,6 @@ class LoginCheckpointContainer extends React.PureComponent<RouteComponentProps,
|
||||||
isLoading: false,
|
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 () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -64,19 +29,6 @@ class LoginCheckpointContainer extends React.PureComponent<RouteComponentProps,
|
||||||
code from your device in order to continue.
|
code from your device in order to continue.
|
||||||
</p>
|
</p>
|
||||||
<div className={'flex mt-6'}>
|
<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>
|
||||||
<div className={'mt-6'}>
|
<div className={'mt-6'}>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import OpenInputField from '@/components/forms/OpenInputField';
|
|
||||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||||
import login from '@/api/auth/login';
|
import login from '@/api/auth/login';
|
||||||
import { httpErrorToHuman } from '@/api/http';
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
|
@ -65,23 +64,22 @@ export default class LoginContainer extends React.PureComponent<RouteComponentPr
|
||||||
</h2>
|
</h2>
|
||||||
<NetworkErrorMessage message={this.state.errorMessage}/>
|
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||||
<form className={'login-box'} onSubmit={this.submit}>
|
<form className={'login-box'} onSubmit={this.submit}>
|
||||||
<div className={'mt-3'}>
|
<label htmlFor={'username'}>Username or Email</label>
|
||||||
<OpenInputField
|
<input
|
||||||
autoFocus={true}
|
id={'username'}
|
||||||
label={'Username or Email'}
|
autoFocus={true}
|
||||||
type={'text'}
|
required={true}
|
||||||
required={true}
|
className={'input'}
|
||||||
id={'username'}
|
onChange={this.handleFieldUpdate}
|
||||||
onChange={this.handleFieldUpdate}
|
disabled={this.state.isLoading}
|
||||||
disabled={this.state.isLoading}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={'mt-6'}>
|
<div className={'mt-6'}>
|
||||||
<OpenInputField
|
<label htmlFor={'password'}>Password</label>
|
||||||
label={'Password'}
|
<input
|
||||||
type={'password'}
|
|
||||||
required={true}
|
|
||||||
id={'password'}
|
id={'password'}
|
||||||
|
required={true}
|
||||||
|
type={'password'}
|
||||||
|
className={'input'}
|
||||||
onChange={this.handleFieldUpdate}
|
onChange={this.handleFieldUpdate}
|
||||||
disabled={this.state.isLoading}
|
disabled={this.state.isLoading}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import OpenInputField from '@/components/forms/OpenInputField';
|
|
||||||
import { RouteComponentProps } from 'react-router';
|
import { RouteComponentProps } from 'react-router';
|
||||||
import { parse } from 'query-string';
|
import { parse } from 'query-string';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
@ -93,30 +92,26 @@ class ResetPasswordContainer extends React.PureComponent<Props, State> {
|
||||||
</h2>
|
</h2>
|
||||||
<NetworkErrorMessage message={this.state.errorMessage}/>
|
<NetworkErrorMessage message={this.state.errorMessage}/>
|
||||||
<form className={'login-box'} onSubmit={this.onSubmit}>
|
<form className={'login-box'} onSubmit={this.onSubmit}>
|
||||||
<div className={'mt-3'}>
|
<label>Email</label>
|
||||||
<OpenInputField
|
<input value={this.state.email || ''} disabled={true}/>
|
||||||
label={'Email'}
|
|
||||||
value={this.state.email || ''}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={'mt-6'}>
|
<div className={'mt-6'}>
|
||||||
<OpenInputField
|
<label htmlFor={'new-password'}>New Password</label>
|
||||||
autoFocus={true}
|
<input
|
||||||
label={'New Password'}
|
id={'new-password'}
|
||||||
description={'Passwords must be at least 8 characters in length.'}
|
|
||||||
type={'password'}
|
type={'password'}
|
||||||
required={true}
|
required={true}
|
||||||
id={'password'}
|
|
||||||
onChange={this.onPasswordChange}
|
onChange={this.onPasswordChange}
|
||||||
/>
|
/>
|
||||||
|
<p className={'input-help'}>
|
||||||
|
Passwords must be at least 8 characters in length.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6'}>
|
<div className={'mt-6'}>
|
||||||
<OpenInputField
|
<label htmlFor={'new-password-confirm'}>Confirm New Password</label>
|
||||||
label={'Confirm New Password'}
|
<input
|
||||||
|
id={'new-password-confirm'}
|
||||||
type={'password'}
|
type={'password'}
|
||||||
required={true}
|
required={true}
|
||||||
id={'password-confirm'}
|
|
||||||
onChange={this.onPasswordConfirmChange}
|
onChange={this.onPasswordConfirmChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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…
Add table
Reference in a new issue