feat(ui): add global progress bar for navigation feedback
CI / check (push) Failing after 25s
CI / deploy (push) Skipped
CI / publish-container (push) Skipped

This commit is contained in:
openhands committed 2026-09-13 14:44:46 +02:00
1 parent d78744efc1
commit 966a925614
2 files changed
+44 -1

No files matched your search

+5 -1
View File
@@ -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} />
+39
View File
@@ -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>
);
}