feat: add LoadingBar component with loading animation
This commit is contained in:
@@ -2,12 +2,14 @@ import { useState } from "react";
|
||||
import { Outlet } from "@tanstack/react-router";
|
||||
import { Header } from "./Header";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { LoadingBar } from "@/components/ui/loading-bar";
|
||||
|
||||
export function Layout() {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen">
|
||||
<LoadingBar />
|
||||
<Sidebar open={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
||||
<div className="flex flex-1 flex-col">
|
||||
<Header onMenuClick={() => setSidebarOpen(true)} />
|
||||
|
||||
37
apps/frontend/src/components/ui/loading-bar.tsx
Normal file
37
apps/frontend/src/components/ui/loading-bar.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useRouterState } from "@tanstack/react-router";
|
||||
import { useIsFetching, useIsMutating } from "@tanstack/react-query";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function LoadingBar() {
|
||||
const isNavigating = useRouterState({ select: (s) => s.status === "pending" });
|
||||
const isFetching = useIsFetching();
|
||||
const isMutating = useIsMutating();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||
|
||||
const loading = isNavigating || isFetching > 0 || isMutating > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
timerRef.current = setTimeout(() => setVisible(true), 200);
|
||||
} else {
|
||||
clearTimeout(timerRef.current);
|
||||
setVisible(false);
|
||||
}
|
||||
return () => clearTimeout(timerRef.current);
|
||||
}, [loading]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"fixed top-0 left-0 right-0 z-[100] h-0.5 transition-opacity duration-300",
|
||||
visible ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="loading-bar h-full w-full bg-primary" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -114,6 +114,28 @@
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
||||
@keyframes loading-bar {
|
||||
0% {
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
}
|
||||
50% {
|
||||
transform: scaleX(1);
|
||||
transform-origin: left;
|
||||
}
|
||||
51% {
|
||||
transform-origin: right;
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(0);
|
||||
transform-origin: right;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
animation: loading-bar 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
* {
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user