feat: add loading indicator
This commit is contained in:
parent
6102fabfcb
commit
f563335fab
@ -1,7 +1,8 @@
|
|||||||
import { NavLink } from '@remix-run/react'
|
import { NavLink } from '@remix-run/react'
|
||||||
import clsx from 'clsx'
|
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { cn } from '~/utils/cn'
|
||||||
|
|
||||||
type Properties = {
|
type Properties = {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly to: string;
|
readonly to: string;
|
||||||
@ -12,13 +13,20 @@ export default function TabLink({ name, to, icon }: Properties) {
|
|||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
to={to}
|
to={to}
|
||||||
className={({ isActive, isPending }) => clsx(
|
prefetch='intent'
|
||||||
'flex items-center gap-x-2 p-2 border-b-2 text-md text-nowrap',
|
className={({ isActive }) => cn(
|
||||||
isActive ? 'border-white' : 'border-transparent',
|
'border-b-2 py-1.5',
|
||||||
isPending && 'animate-pulse'
|
isActive ? 'border-white' : 'border-transparent'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{icon} {name}
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-x-2 px-2.5 py-1.5 text-md text-nowrap',
|
||||||
|
'hover:bg-ui-100/5 dark:hover:bg-ui-900/40 rounded-md'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{icon} {name}
|
||||||
|
</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
import { type LoaderFunctionArgs, redirect } from '@remix-run/node'
|
||||||
import { Outlet, useLoaderData } from '@remix-run/react'
|
import { Outlet, useLoaderData, useNavigation } from '@remix-run/react'
|
||||||
|
import { ProgressBar } from 'react-aria-components'
|
||||||
|
|
||||||
import { ErrorPopup } from '~/components/Error'
|
import { ErrorPopup } from '~/components/Error'
|
||||||
import Header from '~/components/Header'
|
import Header from '~/components/Header'
|
||||||
|
import { cn } from '~/utils/cn'
|
||||||
import { getContext } from '~/utils/config'
|
import { getContext } from '~/utils/config'
|
||||||
import { HeadscaleError, pull } from '~/utils/headscale'
|
import { HeadscaleError, pull } from '~/utils/headscale'
|
||||||
import { destroySession, getSession } from '~/utils/sessions'
|
import { destroySession, getSession } from '~/utils/sessions'
|
||||||
@ -40,11 +42,22 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const data = useLoaderData<typeof loader>()
|
const data = useLoaderData<typeof loader>()
|
||||||
|
const nav = useNavigation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<ProgressBar
|
||||||
|
aria-label='Loading...'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'fixed top-0 left-0 z-50 w-1/2 h-1',
|
||||||
|
'bg-blue-500 dark:bg-blue-400 opacity-0',
|
||||||
|
nav.state === 'loading' && 'animate-loading opacity-100'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</ProgressBar>
|
||||||
<Header data={data}/>
|
<Header data={data}/>
|
||||||
|
|
||||||
<main className='container mx-auto overscroll-contain mt-4 mb-24'>
|
<main className='container mx-auto overscroll-contain mt-4 mb-24'>
|
||||||
<Outlet/>
|
<Outlet/>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -24,6 +24,19 @@ export default {
|
|||||||
colors: {
|
colors: {
|
||||||
main: colors.slate,
|
main: colors.slate,
|
||||||
ui: colors.neutral
|
ui: colors.neutral
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
loader: {
|
||||||
|
from: {
|
||||||
|
transform: 'translateX(-100%)'
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
transform: 'translateX(100%)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
loading: 'loader 0.8s infinite ease-in-out'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user