feat(Layout): add loading spinner component and to layout

Reusable spinner component

Apply spinner to layout to show a "loading state" between transitions, the spinner only shows if the transition is taking more than .2 secconds
This commit is contained in:
Alexander Navarro 2024-03-07 17:20:45 -03:00
parent d770f51948
commit a1528a2ad8
3 changed files with 109 additions and 1 deletions

View file

@ -102,3 +102,7 @@ li:not(:last-child) {
height: 100% !important; height: 100% !important;
} }
} }
.d-none {
display: none;
}

View file

@ -0,0 +1,63 @@
---
interface Props {
size?: number;
color?: string;
bgColor?: string;
}
const {size = 200, color= "#cad3f5", bgColor} = Astro.props;
---
<div class="spinner">
<div class="container">
<svg viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path class="animation animation-normal" d="M4 24C4 35.0457 12.9543 44 24 44V44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4" stroke={color} stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
<path class="animation animation-reverse" d="M36 24C36 17.3726 30.6274 12 24 12C17.3726 12 12 17.3726 12 24C12 30.6274 17.3726 36 24 36V36" stroke={color} stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</svg>
</div>
</div>
<style define:vars={{size: `${size}px`, bgColor: bgColor ?? "var(--prj-bg)"}}>
.spinner {
background: var(--bgColor);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
margin: 10px 0px -10px 0px;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.container {
width: var(--size);
}
.animation {
animation: rotate 1.5s linear infinite;
transform-box: fill-box;
transform-origin: center;
}
.animation-normal {
animation-direction: normal;
}
.animation-reverse {
animation-direction: reverse;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
</style>

View file

@ -4,9 +4,11 @@ export interface Props {
title: string; title: string;
} }
const { title } = Astro.props;
import '../assets/style/global.css'; import '../assets/style/global.css';
import Navbar from '../components/Navbar.astro'; import Navbar from '../components/Navbar.astro';
import Spinner from '../components/Spinner.astro';
const { title } = Astro.props;
--- ---
<!DOCTYPE html> <!DOCTYPE html>
@ -28,12 +30,51 @@ import Navbar from '../components/Navbar.astro';
<Navbar /> <Navbar />
</header> </header>
<main transition:animate="fade"> <main transition:animate="fade">
<div id="layout-loading-spinner" class="d-none">
<Spinner />
</div>
<slot /> <slot />
</main> </main>
<style> <style>
header > :global(*) { header > :global(*) {
margin-left: auto; margin-left: auto;
} }
body > main {
position: relative;
}
/* Position spinner in the center of the screen instead of the center of it's parent */
/* This is because the height of the main div can change */
/* but we still want the background to hide the content */
#layout-loading-spinner :global(.spinner) :global(svg) {
position: fixed;
width: 200px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style> </style>
<script is:inline>
document.addEventListener('astro:before-preparation', (ev) => {
const originalLoader = ev.loader;
ev.loader = async function () {
const spinner = document.querySelector('#layout-loading-spinner');
// Only show the animation if page load is > than timeout seconds
const timeoutId = setTimeout(
() => spinner.classList.remove('d-none'),
200,
);
await originalLoader();
// cancel timeout if is not run yet
clearTimeout(timeoutId);
// spinner.classList.add('d-none');
};
});
</script>
</body> </body>
</html> </html>