From 7370e6c128b819bf51260783c6ffbe47be8e0b47 Mon Sep 17 00:00:00 2001 From: yyamashita Date: Tue, 14 Jul 2026 23:09:27 +0900 Subject: Add navigation progress bar for client-side transitions Co-Authored-By: Claude Sonnet 4.6 --- app/app.css | 5 +++++ app/root.tsx | 42 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/app.css b/app/app.css index 99345d8..24e2c92 100644 --- a/app/app.css +++ b/app/app.css @@ -5,6 +5,11 @@ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } +@keyframes nav-progress { + from { transform: scaleX(0.05); } + to { transform: scaleX(0.85); } +} + html, body { @apply bg-white dark:bg-gray-950; diff --git a/app/root.tsx b/app/root.tsx index 37827e4..50fca78 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from "react"; import { isRouteErrorResponse, Links, @@ -5,6 +6,7 @@ import { Outlet, Scripts, ScrollRestoration, + useNavigation, } from "react-router"; import type { Route } from "./+types/root"; @@ -42,8 +44,46 @@ export function Layout({ children }: { children: React.ReactNode }) { ); } +function NavigationProgress() { + const navigation = useNavigation(); + const [phase, setPhase] = useState<"idle" | "loading" | "done">("idle"); + + useEffect(() => { + if (navigation.state !== "idle") { + setPhase("loading"); + } else { + setPhase((prev) => (prev === "loading" ? "done" : prev)); + } + }, [navigation.state]); + + useEffect(() => { + if (phase !== "done") return; + const t = setTimeout(() => setPhase("idle"), 500); + return () => clearTimeout(t); + }, [phase]); + + if (phase === "idle") return null; + + return ( +
+ ); +} + export default function App() { - return ; + return ( + <> + + + + ); } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { -- cgit v1.2.3