Add animations to loaders.

This commit is contained in:
Paul 2021-06-22 16:29:47 +01:00
parent e98a962071
commit 22b21c030f
16 changed files with 123 additions and 21 deletions

View file

@ -49,7 +49,7 @@ export default function TextFile({ attachment }: Props) {
content ? content ?
<pre><code>{ content }</code></pre> <pre><code>{ content }</code></pre>
: <RequiresOnline> : <RequiresOnline>
<Preloader /> <Preloader type="ring" />
</RequiresOnline> </RequiresOnline>
} }
</div> </div>

View file

@ -1,3 +1,103 @@
export default function Preloader() { import styled, { keyframes } from "styled-components";
return <span>LOADING</span>;
const skSpinner = keyframes`
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
}
40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
`;
const prRing = keyframes`
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
`;
const PreloaderBase = styled.div`
width: 100%;
height: 100%;
display: grid;
place-items: center;
.spinner {
width: 58px;
display: flex;
text-align: center;
margin: 100px auto 0;
justify-content: space-between;
}
.spinner > div {
width: 14px;
height: 14px;
background-color: var(--tertiary-foreground);
border-radius: 100%;
display: inline-block;
animation: ${skSpinner} 1.4s infinite ease-in-out both;
}
.spinner div:nth-child(1) {
animation-delay: -0.32s;
}
.spinner div:nth-child(2) {
animation-delay: -0.16s;
}
.ring {
display: inline-block;
position: relative;
width: 48px;
height: 52px;
}
.ring div {
width: 32px;
margin: 8px;
height: 32px;
display: block;
position: absolute;
border-radius: 50%;
box-sizing: border-box;
border: 2px solid #fff;
animation: ${prRing} 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.ring div:nth-child(1) {
animation-delay: -0.45s;
}
.ring div:nth-child(2) {
animation-delay: -0.3s;
}
.ring div:nth-child(3) {
animation-delay: -0.15s;
}
`;
interface Props {
type: 'spinner' | 'ring'
}
export default function Preloader({ type }: Props) {
return (
<PreloaderBase>
<div class={type}>
<div />
<div />
<div />
</div>
</PreloaderBase>
);
} }

View file

@ -39,7 +39,7 @@ export function OnboardingModal({ onClose, callback }: Props) {
</div> </div>
<div className={styles.form}> <div className={styles.form}>
{loading ? ( {loading ? (
<Preloader /> <Preloader type="spinner" />
) : ( ) : (
<> <>
<p> <p>

View file

@ -309,7 +309,7 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) {
)} )}
</div> </div>
) : ( ) : (
<Preloader /> <Preloader type="ring" />
))} ))}
{tab === "groups" && ( {tab === "groups" && (
<div className={styles.entries}> <div className={styles.entries}>

View file

@ -187,7 +187,7 @@ export function FileUploader(props: Props) {
onClick={onClick}> onClick={onClick}>
{ uploading ? { uploading ?
<div className={styles.uploading}> <div className={styles.uploading}>
<Preloader /> <Preloader type="ring" />
</div> : </div> :
<div className={styles.edit}> <div className={styles.edit}>
<Edit size={30} /> <Edit size={30} />

View file

@ -29,7 +29,7 @@ const Base = styled.div`
export default function RequiresOnline(props: Props) { export default function RequiresOnline(props: Props) {
const status = useContext(StatusContext); const status = useContext(StatusContext);
if (status === ClientStatus.CONNECTING) return <Preloader />; if (status === ClientStatus.CONNECTING) return <Preloader type="ring" />;
if (status !== ClientStatus.ONLINE && status !== ClientStatus.READY) if (status !== ClientStatus.ONLINE && status !== ClientStatus.READY)
return ( return (
<Base> <Base>

View file

@ -202,7 +202,7 @@ function Context({ auth, sync, children, dispatcher }: Props) {
}, []); }, []);
if (status === ClientStatus.LOADING) { if (status === ClientStatus.LOADING) {
return <Preloader />; return <Preloader type="spinner" />;
} }
return ( return (

View file

@ -34,13 +34,15 @@ const Routes = styled.div`
export default function App() { export default function App() {
const path = useLocation().pathname; const path = useLocation().pathname;
const fixedBottomNav = (path === '/' || path === '/settings' || path.startsWith("/friends")); const fixedBottomNav = (path === '/' || path === '/settings' || path.startsWith("/friends"));
const inSettings = path === '/settings';
const inChannel = path.includes('/channel');
return ( return (
<OverlappingPanels <OverlappingPanels
width="100vw" width="100vw"
height="100vh" height="100vh"
leftPanel={{ width: 292, component: <LeftSidebar /> }} leftPanel={inSettings ? undefined : { width: 292, component: <LeftSidebar /> }}
rightPanel={{ width: 240, component: <RightSidebar /> }} rightPanel={(!inSettings && inChannel) ? { width: 240, component: <RightSidebar /> } : undefined}
bottomNav={{ bottomNav={{
component: <BottomNavigation />, component: <BottomNavigation />,
showIf: fixedBottomNav ? ShowIf.Always : ShowIf.Left, showIf: fixedBottomNav ? ShowIf.Always : ShowIf.Left,

View file

@ -12,7 +12,7 @@ export function App() {
<Context> <Context>
{/* {/*
// @ts-expect-error */} // @ts-expect-error */}
<Suspense fallback={<Preloader />}> <Suspense fallback={<Preloader type="spinner" />}>
<Switch> <Switch>
<Route path="/login"> <Route path="/login">
<CheckAuth> <CheckAuth>

View file

@ -215,10 +215,10 @@ export function MessageArea({ id }: Props) {
<MessageAreaWidthContext.Provider value={(width ?? 0) - MESSAGE_AREA_PADDING}> <MessageAreaWidthContext.Provider value={(width ?? 0) - MESSAGE_AREA_PADDING}>
<Area ref={ref}> <Area ref={ref}>
<div> <div>
{state.type === "LOADING" && <Preloader />} {state.type === "LOADING" && <Preloader type="ring" />}
{state.type === "WAITING_FOR_NETWORK" && ( {state.type === "WAITING_FOR_NETWORK" && (
<RequiresOnline> <RequiresOnline>
<Preloader /> <Preloader type="ring" />
</RequiresOnline> </RequiresOnline>
)} )}
{state.type === "RENDER" && ( {state.type === "RENDER" && (

View file

@ -60,7 +60,7 @@ function MessageRenderer({ id, state, queue }: Props) {
} else { } else {
render.push( render.push(
<RequiresOnline> <RequiresOnline>
<Preloader /> <Preloader type="ring" />
</RequiresOnline> </RequiresOnline>
); );
} }
@ -148,7 +148,7 @@ function MessageRenderer({ id, state, queue }: Props) {
} else { } else {
render.push( render.push(
<RequiresOnline> <RequiresOnline>
<Preloader /> <Preloader type="ring" />
</RequiresOnline> </RequiresOnline>
); );
} }

View file

@ -34,7 +34,7 @@ export default function Invite() {
<div className={styles.preloader}> <div className={styles.preloader}>
<RequiresOnline> <RequiresOnline>
{ error ? <Overline type="error" error={error} /> { error ? <Overline type="error" error={error} />
: <Preloader /> } : <Preloader type="spinner" /> }
</RequiresOnline> </RequiresOnline>
</div> </div>
) )
@ -52,7 +52,7 @@ export default function Invite() {
</div> } </div> }
<div className={styles.details}> <div className={styles.details}>
{ processing ? <Preloader /> : { processing ? <Preloader type="ring" /> :
<> <>
<h1>{ invite.server_name }</h1> <h1>{ invite.server_name }</h1>
<h2>#{invite.channel_name}</h2> <h2>#{invite.channel_name}</h2>

View file

@ -20,7 +20,7 @@ export function CaptchaBlock(props: CaptchaProps) {
}, []); }, []);
if (!client.configuration?.features.captcha.enabled) if (!client.configuration?.features.captcha.enabled)
return <Preloader />; return <Preloader type="spinner" />;
return ( return (
<div> <div>

View file

@ -138,7 +138,7 @@ export function Form({ page, callback }: Props) {
} }
if (captcha) return <CaptchaBlock {...captcha} />; if (captcha) return <CaptchaBlock {...captcha} />;
if (loading) return <Preloader />; if (loading) return <Preloader type="spinner" />;
return ( return (
<div className={styles.form}> <div className={styles.form}>

View file

@ -55,7 +55,7 @@ export function Sessions() {
if (typeof sessions === "undefined") { if (typeof sessions === "undefined") {
return ( return (
<div className={styles.loader}> <div className={styles.loader}>
<Preloader /> <Preloader type="ring" />
</div> </div>
); );
} }

View file

@ -27,7 +27,7 @@ export function Invites({ server }: Props) {
return ( return (
<div className={styles.invites}> <div className={styles.invites}>
{ typeof invites === 'undefined' && <Preloader /> } { typeof invites === 'undefined' && <Preloader type="ring" /> }
{ {
invites?.map( invites?.map(
invite => { invite => {