Correctly center the spinner in buttons
This commit is contained in:
parent
8c20158e58
commit
1c97dd4e24
4 changed files with 40 additions and 51 deletions
|
@ -5,12 +5,12 @@ import { httpErrorToHuman } from '@/api/http';
|
||||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
|
import LoginFormContainer from '@/components/auth/LoginFormContainer';
|
||||||
import { ActionCreator } from 'easy-peasy';
|
import { ActionCreator } from 'easy-peasy';
|
||||||
import { StaticContext } from 'react-router';
|
import { StaticContext } from 'react-router';
|
||||||
import Spinner from '@/components/elements/Spinner';
|
|
||||||
import { useFormikContext, withFormik } from 'formik';
|
import { useFormikContext, withFormik } from 'formik';
|
||||||
import { object, string } from 'yup';
|
|
||||||
import useFlash from '@/plugins/useFlash';
|
import useFlash from '@/plugins/useFlash';
|
||||||
import { FlashStore } from '@/state/flashes';
|
import { FlashStore } from '@/state/flashes';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import Button from '@/components/elements/Button';
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
code: string;
|
code: string;
|
||||||
|
@ -29,13 +29,10 @@ const LoginCheckpointContainer = () => {
|
||||||
const [ isMissingDevice, setIsMissingDevice ] = useState(false);
|
const [ isMissingDevice, setIsMissingDevice ] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoginFormContainer
|
<LoginFormContainer title={'Device Checkpoint'} css={tw`w-full flex`}>
|
||||||
title={'Device Checkpoint'}
|
<div css={tw`mt-6`}>
|
||||||
className={'w-full flex'}
|
|
||||||
>
|
|
||||||
<div className={'mt-6'}>
|
|
||||||
<Field
|
<Field
|
||||||
light={true}
|
light
|
||||||
name={isMissingDevice ? 'recoveryCode' : 'code'}
|
name={isMissingDevice ? 'recoveryCode' : 'code'}
|
||||||
title={isMissingDevice ? 'Recovery Code' : 'Authentication Code'}
|
title={isMissingDevice ? 'Recovery Code' : 'Authentication Code'}
|
||||||
description={
|
description={
|
||||||
|
@ -44,38 +41,35 @@ const LoginCheckpointContainer = () => {
|
||||||
: 'Enter the two-factor token generated by your device.'
|
: 'Enter the two-factor token generated by your device.'
|
||||||
}
|
}
|
||||||
type={isMissingDevice ? 'text' : 'number'}
|
type={isMissingDevice ? 'text' : 'number'}
|
||||||
autoFocus={true}
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6'}>
|
<div css={tw`mt-6`}>
|
||||||
<button
|
<Button
|
||||||
|
size={'xlarge'}
|
||||||
type={'submit'}
|
type={'submit'}
|
||||||
className={'btn btn-primary btn-jumbo'}
|
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
isLoading={isSubmitting}
|
||||||
>
|
>
|
||||||
{isSubmitting ?
|
Continue
|
||||||
<Spinner size={'small'} className={'mx-auto'}/>
|
</Button>
|
||||||
:
|
|
||||||
'Continue'
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6 text-center'}>
|
<div css={tw`mt-6 text-center`}>
|
||||||
<span
|
<span
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFieldValue('code', '');
|
setFieldValue('code', '');
|
||||||
setFieldValue('recoveryCode', '');
|
setFieldValue('recoveryCode', '');
|
||||||
setIsMissingDevice(s => !s);
|
setIsMissingDevice(s => !s);
|
||||||
}}
|
}}
|
||||||
className={'cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
|
css={tw`cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`}
|
||||||
>
|
>
|
||||||
{!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'}
|
{!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6 text-center'}>
|
<div css={tw`mt-6 text-center`}>
|
||||||
<Link
|
<Link
|
||||||
to={'/auth/login'}
|
to={'/auth/login'}
|
||||||
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
|
css={tw`text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`}
|
||||||
>
|
>
|
||||||
Return to Login
|
Return to Login
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -36,11 +36,7 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{ref.current && ref.current.render()}
|
{ref.current && ref.current.render()}
|
||||||
<LoginFormContainer
|
<LoginFormContainer title={'Login to Continue'} css={tw`w-full flex`} onSubmit={submit}>
|
||||||
title={'Login to Continue'}
|
|
||||||
css={tw`w-full flex`}
|
|
||||||
onSubmit={submit}
|
|
||||||
>
|
|
||||||
<Field
|
<Field
|
||||||
type={'text'}
|
type={'text'}
|
||||||
label={'Username or Email'}
|
label={'Username or Email'}
|
||||||
|
|
|
@ -11,15 +11,16 @@ import Spinner from '@/components/elements/Spinner';
|
||||||
import { Formik, FormikHelpers } from 'formik';
|
import { Formik, FormikHelpers } from 'formik';
|
||||||
import { object, ref, string } from 'yup';
|
import { object, ref, string } from 'yup';
|
||||||
import Field from '@/components/elements/Field';
|
import Field from '@/components/elements/Field';
|
||||||
|
import Input from '@/components/elements/Input';
|
||||||
type Props = Readonly<RouteComponentProps<{ token: string }> & {}>;
|
import tw from 'twin.macro';
|
||||||
|
import Button from '@/components/elements/Button';
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
password: string;
|
password: string;
|
||||||
passwordConfirmation: string;
|
passwordConfirmation: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ match, history, location }: Props) => {
|
export default ({ match, location }: RouteComponentProps<{ token: string }>) => {
|
||||||
const [ email, setEmail ] = useState('');
|
const [ email, setEmail ] = useState('');
|
||||||
|
|
||||||
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||||
|
@ -62,46 +63,43 @@ export default ({ match, history, location }: Props) => {
|
||||||
{({ isSubmitting }) => (
|
{({ isSubmitting }) => (
|
||||||
<LoginFormContainer
|
<LoginFormContainer
|
||||||
title={'Reset Password'}
|
title={'Reset Password'}
|
||||||
className={'w-full flex'}
|
css={tw`w-full flex`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label>Email</label>
|
<label>Email</label>
|
||||||
<input className={'input'} value={email} disabled={true}/>
|
<Input value={email} light disabled/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6'}>
|
<div css={tw`mt-6`}>
|
||||||
<Field
|
<Field
|
||||||
light={true}
|
light
|
||||||
label={'New Password'}
|
label={'New Password'}
|
||||||
name={'password'}
|
name={'password'}
|
||||||
type={'password'}
|
type={'password'}
|
||||||
description={'Passwords must be at least 8 characters in length.'}
|
description={'Passwords must be at least 8 characters in length.'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6'}>
|
<div css={tw`mt-6`}>
|
||||||
<Field
|
<Field
|
||||||
light={true}
|
light
|
||||||
label={'Confirm New Password'}
|
label={'Confirm New Password'}
|
||||||
name={'passwordConfirmation'}
|
name={'passwordConfirmation'}
|
||||||
type={'password'}
|
type={'password'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6'}>
|
<div css={tw`mt-6`}>
|
||||||
<button
|
<Button
|
||||||
|
size={'xlarge'}
|
||||||
type={'submit'}
|
type={'submit'}
|
||||||
className={'btn btn-primary btn-jumbo'}
|
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
isLoading={isSubmitting}
|
||||||
>
|
>
|
||||||
{isSubmitting ?
|
Reset Password
|
||||||
<Spinner size={'small'} className={'mx-auto'}/>
|
</Button>
|
||||||
:
|
|
||||||
'Reset Password'
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={'mt-6 text-center'}>
|
<div css={tw`mt-6 text-center`}>
|
||||||
<Link
|
<Link
|
||||||
to={'/auth/login'}
|
to={'/auth/login'}
|
||||||
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'}
|
css={tw`text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600`}
|
||||||
>
|
>
|
||||||
Return to Login
|
Return to Login
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled, { css } from 'styled-components/macro';
|
import styled, { css } from 'styled-components/macro';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
import Spinner from '@/components/elements/Spinner';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
@ -10,9 +11,9 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledButton = styled.button<Omit<Props, 'isLoading'>>`
|
const StyledButton = styled.button<Omit<Props, 'isLoading'>>`
|
||||||
${tw`rounded p-2 uppercase tracking-wide text-sm transition-all duration-150`};
|
${tw`relative inline-block rounded p-2 uppercase tracking-wide text-sm transition-all duration-150`};
|
||||||
|
|
||||||
${props => props.isSecondary && css<Props>`
|
${props => props.isSecondary && css<Props>`
|
||||||
${tw`border border-neutral-600 bg-transparent text-neutral-200`};
|
${tw`border border-neutral-600 bg-transparent text-neutral-200`};
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
|
@ -79,8 +80,8 @@ type ComponentProps = Omit<JSX.IntrinsicElements['button'], 'ref' | keyof Props>
|
||||||
const Button: React.FC<ComponentProps> = ({ children, isLoading, ...props }) => (
|
const Button: React.FC<ComponentProps> = ({ children, isLoading, ...props }) => (
|
||||||
<StyledButton {...props}>
|
<StyledButton {...props}>
|
||||||
{isLoading &&
|
{isLoading &&
|
||||||
<div css={tw`w-full flex absolute justify-center`} style={{ marginLeft: '-0.75rem' }}>
|
<div css={tw`flex absolute justify-center items-center w-full h-full left-0 top-0`}>
|
||||||
<div className={'spinner-circle spinner-white spinner-sm'}/>
|
<Spinner size={'small'}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<span css={isLoading ? tw`text-transparent` : undefined}>
|
<span css={isLoading ? tw`text-transparent` : undefined}>
|
||||||
|
|
Loading…
Reference in a new issue