feat(ui): add global progress bar for navigation feedback
This commit is contained in:
1 parent
d78744efc1
commit
966a925614
2 files changed
+44
-1
No files matched your search
+5
-1
@@ -5,8 +5,9 @@ import Script from "next/script";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getLocale, getMessages } from "next-intl/server";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { type ReactNode, Suspense } from "react";
|
||||
import { Toaster } from "sonner";
|
||||
import { GlobalProgressBar } from "@/components/global-progress-bar";
|
||||
import { PwaRegister } from "@/components/pwa-register";
|
||||
import { ScopedThemeVars } from "@/components/scoped-theme-vars";
|
||||
import { ThemeScopeDetector } from "@/components/theme-scope-detector";
|
||||
@@ -96,6 +97,9 @@ export default async function RootLayout({
|
||||
className="flex min-h-screen flex-col site-bg"
|
||||
style={{ backgroundColor: "var(--color-background)" }}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
<GlobalProgressBar />
|
||||
</Suspense>
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
<ThemeVars nonce={nonce} />
|
||||
<ScopedThemeVars nonce={nonce} />
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useIsPresent } from "motion/react";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* Global smooth progress indicator for page transitions.
|
||||
* Uses motion/react for high-performance 60fps animations.
|
||||
*/
|
||||
export function GlobalProgressBar() {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const isPresent = useIsPresent();
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 400);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
if (!isLoading && isPresent) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed top-0 left-0 right-0 z-[99999] h-1 bg-transparent pointer-events-none overflow-hidden">
|
||||
<motion.div
|
||||
className="h-full bg-[var(--color-primary)] shadow-[0_0_10px_var(--color-primary)]"
|
||||
initial={{ width: "0%", opacity: 1 }}
|
||||
animate={{ width: "85%", opacity: 1 }}
|
||||
exit={{ width: "100%", opacity: 0 }}
|
||||
transition={{ duration: 0.3, ease: "easeInOut" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user