Get spinners back in working order
This commit is contained in:
parent
d260200947
commit
5473edc006
16 changed files with 74 additions and 50 deletions
|
@ -2,13 +2,13 @@ import React from 'react';
|
||||||
import MessageBox from '@/components/MessageBox';
|
import MessageBox from '@/components/MessageBox';
|
||||||
import { State, useStoreState } from 'easy-peasy';
|
import { State, useStoreState } from 'easy-peasy';
|
||||||
import { ApplicationStore } from '@/state';
|
import { ApplicationStore } from '@/state';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
byKey?: string;
|
byKey?: string;
|
||||||
spacerClass?: string;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default ({ spacerClass, byKey }: Props) => {
|
export default ({ byKey }: Props) => {
|
||||||
const flashes = useStoreState((state: State<ApplicationStore>) => state.flashes.items);
|
const flashes = useStoreState((state: State<ApplicationStore>) => state.flashes.items);
|
||||||
|
|
||||||
let filtered = flashes;
|
let filtered = flashes;
|
||||||
|
@ -25,7 +25,7 @@ export default ({ spacerClass, byKey }: Props) => {
|
||||||
{
|
{
|
||||||
filtered.map((flash, index) => (
|
filtered.map((flash, index) => (
|
||||||
<React.Fragment key={flash.id || flash.type + flash.message}>
|
<React.Fragment key={flash.id || flash.type + flash.message}>
|
||||||
{index > 0 && <div css={tw`${spacerClass || 'mt-2'}`}></div>}
|
{index > 0 && <div css={tw`mt-2`}></div>}
|
||||||
<MessageBox type={flash.type} title={flash.title}>
|
<MessageBox type={flash.type} title={flash.title}>
|
||||||
{flash.message}
|
{flash.message}
|
||||||
</MessageBox>
|
</MessageBox>
|
||||||
|
|
|
@ -54,7 +54,7 @@ const LoginCheckpointContainer = () => {
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
{isSubmitting ?
|
{isSubmitting ?
|
||||||
<Spinner size={'tiny'} className={'mx-auto'}/>
|
<Spinner size={'small'} className={'mx-auto'}/>
|
||||||
:
|
:
|
||||||
'Continue'
|
'Continue'
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl
|
||||||
className={'btn btn-primary btn-jumbo'}
|
className={'btn btn-primary btn-jumbo'}
|
||||||
>
|
>
|
||||||
{isSubmitting ?
|
{isSubmitting ?
|
||||||
<Spinner size={'tiny'} className={'mx-auto'}/>
|
<Spinner size={'small'} className={'mx-auto'}/>
|
||||||
:
|
:
|
||||||
'Login'
|
'Login'
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ export default ({ match, history, location }: Props) => {
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
{isSubmitting ?
|
{isSubmitting ?
|
||||||
<Spinner size={'tiny'} className={'mx-auto'}/>
|
<Spinner size={'small'} className={'mx-auto'}/>
|
||||||
:
|
:
|
||||||
'Reset Password'
|
'Reset Password'
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ export default ({ server }: { server: Server }) => {
|
||||||
<div css={tw`w-1/3 flex items-baseline relative`}>
|
<div css={tw`w-1/3 flex items-baseline relative`}>
|
||||||
{!stats ?
|
{!stats ?
|
||||||
!statsError ?
|
!statsError ?
|
||||||
<SpinnerOverlay size={'tiny'} visible={true} backgroundOpacity={0.25}/>
|
<SpinnerOverlay size={'small'} visible={true} backgroundOpacity={0.25}/>
|
||||||
:
|
:
|
||||||
server.isInstalling ?
|
server.isInstalling ?
|
||||||
<div css={tw`flex-1 text-center`}>
|
<div css={tw`flex-1 text-center`}>
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { Actions, useStoreActions } from 'easy-peasy';
|
||||||
import { ApplicationStore } from '@/state';
|
import { ApplicationStore } from '@/state';
|
||||||
import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor';
|
import disableAccountTwoFactor from '@/api/account/disableAccountTwoFactor';
|
||||||
import { httpErrorToHuman } from '@/api/http';
|
import { httpErrorToHuman } from '@/api/http';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import Button from '@/components/elements/Button';
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
password: string;
|
password: string;
|
||||||
|
@ -45,19 +47,19 @@ export default ({ ...props }: RequiredModalProps) => {
|
||||||
{({ isSubmitting, isValid }) => (
|
{({ isSubmitting, isValid }) => (
|
||||||
<Modal {...props} dismissable={!isSubmitting} showSpinnerOverlay={isSubmitting}>
|
<Modal {...props} dismissable={!isSubmitting} showSpinnerOverlay={isSubmitting}>
|
||||||
<Form className={'mb-0'}>
|
<Form className={'mb-0'}>
|
||||||
<FlashMessageRender className={'mb-6'} byKey={'account:two-factor'}/>
|
<FlashMessageRender css={tw`mb-6`} byKey={'account:two-factor'}/>
|
||||||
<Field
|
<Field
|
||||||
id={'password'}
|
id={'password'}
|
||||||
name={'password'}
|
name={'password'}
|
||||||
type={'password'}
|
type={'password'}
|
||||||
label={'Current Password'}
|
label={'Current Password'}
|
||||||
description={'In order to disable two-factor authentication you will need to provide your account password.'}
|
description={'In order to disable two-factor authentication you will need to provide your account password.'}
|
||||||
autoFocus={true}
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<div className={'mt-6 text-right'}>
|
<div css={tw`mt-6 text-right`}>
|
||||||
<button className={'btn btn-red btn-sm'} disabled={!isValid}>
|
<Button disabled={!isValid}>
|
||||||
Disable Two-Factor
|
Disable Two-Factor
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -7,10 +7,9 @@ interface Props {
|
||||||
size?: 'xsmall' | 'small' | 'large' | 'xlarge';
|
size?: 'xsmall' | 'small' | 'large' | 'xlarge';
|
||||||
color?: 'green' | 'red' | 'primary' | 'grey';
|
color?: 'green' | 'red' | 'primary' | 'grey';
|
||||||
isSecondary?: boolean;
|
isSecondary?: boolean;
|
||||||
disabled?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledButton = styled.button<Props>`
|
const StyledButton = styled.button<Omit<Props, 'isLoading'>>`
|
||||||
${tw`rounded p-2 uppercase tracking-wide text-sm transition-all duration-150`};
|
${tw`rounded p-2 uppercase tracking-wide text-sm transition-all duration-150`};
|
||||||
|
|
||||||
${props => props.isSecondary && css<Props>`
|
${props => props.isSecondary && css<Props>`
|
||||||
|
@ -73,9 +72,6 @@ const StyledButton = styled.button<Props>`
|
||||||
${props => props.size === 'xlarge' && tw`p-4 w-full`};
|
${props => props.size === 'xlarge' && tw`p-4 w-full`};
|
||||||
|
|
||||||
&:disabled { opacity: 0.55; cursor: default }
|
&:disabled { opacity: 0.55; cursor: default }
|
||||||
|
|
||||||
${props => props.disabled && css`opacity: 0.55; cursor: default`};
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type ComponentProps = Props &
|
type ComponentProps = Props &
|
||||||
|
@ -88,7 +84,7 @@ const Button: React.FC<ComponentProps> = ({ children, isLoading, ...props }) =>
|
||||||
<div className={'spinner-circle spinner-white spinner-sm'}/>
|
<div className={'spinner-circle spinner-white spinner-sm'}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<span css={isLoading && tw`text-transparent`}>
|
<span css={isLoading ? tw`text-transparent` : undefined}>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Field as FormikField, FieldProps } from 'formik';
|
import { Field as FormikField, FieldProps } from 'formik';
|
||||||
import classNames from 'classnames';
|
|
||||||
import Input from '@/components/elements/Input';
|
import Input from '@/components/elements/Input';
|
||||||
|
import Label from '@/components/elements/Label';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -19,7 +19,7 @@ const Field = ({ id, name, light = false, label, description, validate, classNam
|
||||||
({ field, form: { errors, touched } }: FieldProps) => (
|
({ field, form: { errors, touched } }: FieldProps) => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{label &&
|
{label &&
|
||||||
<label htmlFor={id} className={light ? undefined : 'input-dark-label'}>{label}</label>
|
<Label htmlFor={id} isLight={light}>{label}</Label>
|
||||||
}
|
}
|
||||||
<Input
|
<Input
|
||||||
id={id}
|
id={id}
|
||||||
|
|
|
@ -12,7 +12,7 @@ const InputSpinner = ({ visible, children }: { visible: boolean, children: React
|
||||||
classNames={'fade'}
|
classNames={'fade'}
|
||||||
>
|
>
|
||||||
<div className={'absolute right-0 h-full flex items-center justify-end pr-3'}>
|
<div className={'absolute right-0 h-full flex items-center justify-end pr-3'}>
|
||||||
<Spinner size={'tiny'}/>
|
<Spinner size={'small'}/>
|
||||||
</div>
|
</div>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
{children}
|
{children}
|
||||||
|
|
9
resources/scripts/components/elements/Label.tsx
Normal file
9
resources/scripts/components/elements/Label.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import styled from 'styled-components/macro';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
|
const Label = styled.label<{ isLight?: boolean }>`
|
||||||
|
${tw`block text-xs uppercase text-neutral-200 mb-2`};
|
||||||
|
${props => props.isLight && tw`text-neutral-700`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Label;
|
|
@ -1,31 +1,48 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import styled, { css, keyframes } from 'styled-components/macro';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
export type SpinnerSize = 'large' | 'normal' | 'tiny';
|
export type SpinnerSize = 'small' | 'base' | 'large';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
size?: SpinnerSize;
|
size?: SpinnerSize;
|
||||||
centered?: boolean;
|
centered?: boolean;
|
||||||
className?: string;
|
isBlue?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Spinner = ({ size, centered, className }: Props) => (
|
const spin = keyframes`
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
`;
|
||||||
|
|
||||||
|
// noinspection CssOverwrittenProperties
|
||||||
|
const SpinnerComponent = styled.div<Props>`
|
||||||
|
${tw`w-8 h-8`};
|
||||||
|
border-width: 3px;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: ${spin} 1s cubic-bezier(0.55, 0.25, 0.25, 0.70) infinite;
|
||||||
|
|
||||||
|
${props => props.size === 'small' ? tw`w-4 h-4 border-2` : (props.size === 'large' ? css`
|
||||||
|
${tw`w-16 h-16`};
|
||||||
|
border-width: 6px;
|
||||||
|
` : null)};
|
||||||
|
|
||||||
|
border-color: ${props => !props.isBlue ? 'rgba(255, 255, 255, 0.2)' : 'hsla(212, 92%, 43%, 0.2)'};
|
||||||
|
border-top-color: ${props => !props.isBlue ? 'rgb(255, 255, 255)' : 'hsl(212, 92%, 43%)'};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Spinner = ({ centered, ...props }: Props) => (
|
||||||
centered ?
|
centered ?
|
||||||
<div className={classNames(`flex justify-center ${className}`, { 'm-20': size === 'large', 'm-6': size !== 'large' })}>
|
<div
|
||||||
<div
|
css={[
|
||||||
className={classNames('spinner-circle spinner-white', {
|
tw`flex justify-center`,
|
||||||
'spinner-lg': size === 'large',
|
props.size === 'large' ? tw`m-20` : tw`m-6`,
|
||||||
'spinner-sm': size === 'tiny',
|
]}
|
||||||
})}
|
>
|
||||||
/>
|
<SpinnerComponent {...props}/>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<div
|
<SpinnerComponent {...props}/>
|
||||||
className={classNames(`spinner-circle spinner-white ${className}`, {
|
|
||||||
'spinner-lg': size === 'large',
|
|
||||||
'spinner-sm': size === 'tiny',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
Spinner.DisplayName = 'Spinner';
|
||||||
|
|
||||||
export default Spinner;
|
export default Spinner;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { CSSTransition } from 'react-transition-group';
|
|
||||||
import Spinner, { SpinnerSize } from '@/components/elements/Spinner';
|
import Spinner, { SpinnerSize } from '@/components/elements/Spinner';
|
||||||
|
import Fade from '@/components/elements/Fade';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
@ -11,17 +11,17 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpinnerOverlay = ({ size, fixed, visible, backgroundOpacity }: Props) => (
|
const SpinnerOverlay = ({ size, fixed, visible, backgroundOpacity }: Props) => (
|
||||||
<CSSTransition timeout={150} classNames={'fade'} in={visible} unmountOnExit={true}>
|
<Fade timeout={150} in={visible} unmountOnExit>
|
||||||
<div
|
<div
|
||||||
className={classNames('top-0 left-0 flex items-center justify-center w-full h-full rounded', {
|
css={[
|
||||||
absolute: !fixed,
|
tw`top-0 left-0 flex items-center justify-center w-full h-full rounded`,
|
||||||
fixed: fixed,
|
!fixed ? tw`absolute` : tw`fixed`,
|
||||||
})}
|
]}
|
||||||
style={{ zIndex: 9999, background: `rgba(0, 0, 0, ${backgroundOpacity || 0.45})` }}
|
style={{ zIndex: 9999, background: `rgba(0, 0, 0, ${backgroundOpacity || 0.45})` }}
|
||||||
>
|
>
|
||||||
<Spinner size={size}/>
|
<Spinner size={size}/>
|
||||||
</div>
|
</div>
|
||||||
</CSSTransition>
|
</Fade>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default SpinnerOverlay;
|
export default SpinnerOverlay;
|
||||||
|
|
|
@ -5,7 +5,7 @@ const SuspenseSpinner = ({ children }: { children?: React.ReactNode }) => (
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<div className={'mx-4 w-3/4 mr-4 flex items-center justify-center'}>
|
<div className={'mx-4 w-3/4 mr-4 flex items-center justify-center'}>
|
||||||
<Spinner centered={true} size={'normal'}/>
|
<Spinner centered/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -69,7 +69,7 @@ export default () => {
|
||||||
<CSSTransition timeout={250} in={true} appear={true} classNames={'fade'}>
|
<CSSTransition timeout={250} in={true} appear={true} classNames={'fade'}>
|
||||||
<div className={'bg-red-500 py-2'}>
|
<div className={'bg-red-500 py-2'}>
|
||||||
<ContentContainer className={'flex items-center justify-center'}>
|
<ContentContainer className={'flex items-center justify-center'}>
|
||||||
<Spinner size={'tiny'}/>
|
<Spinner size={'small'}/>
|
||||||
<p className={'ml-2 text-sm text-red-100'}>
|
<p className={'ml-2 text-sm text-red-100'}>
|
||||||
We're having some trouble connecting to your server, please wait...
|
We're having some trouble connecting to your server, please wait...
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default ({ backup, className }: Props) => {
|
||||||
{backup.completedAt ?
|
{backup.completedAt ?
|
||||||
<FontAwesomeIcon icon={faArchive} className={'text-neutral-300'}/>
|
<FontAwesomeIcon icon={faArchive} className={'text-neutral-300'}/>
|
||||||
:
|
:
|
||||||
<Spinner size={'tiny'}/>
|
<Spinner size={'small'}/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1'}>
|
<div className={'flex-1'}>
|
||||||
|
|
|
@ -19,7 +19,7 @@ const RenameServerBox = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TitledGreyBox title={'Change Server Name'} className={'relative'}>
|
<TitledGreyBox title={'Change Server Name'} className={'relative'}>
|
||||||
<SpinnerOverlay size={'normal'} visible={isSubmitting}/>
|
<SpinnerOverlay visible={isSubmitting}/>
|
||||||
<Form className={'mb-0'}>
|
<Form className={'mb-0'}>
|
||||||
<Field
|
<Field
|
||||||
id={'name'}
|
id={'name'}
|
||||||
|
|
Loading…
Reference in a new issue