diff --git a/app/root.tsx b/app/root.tsx
index 2d2c6457..e889f1e9 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -25,6 +25,7 @@ import { parseColorScheme } from "~/lib/color-scheme.server";
import iconsHref from "~/icons.svg";
import cx from "clsx";
import { canUseDOM } from "./ui/primitives/utils";
+import { GlobalLoading } from "./ui/global-loading";
export async function loader({ request }: LoaderFunctionArgs) {
removeTrailingSlashes(request);
@@ -128,6 +129,7 @@ function Document({
: "bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-200",
)}
>
+
{children}
diff --git a/app/ui/global-loading.tsx b/app/ui/global-loading.tsx
new file mode 100644
index 00000000..fabec0bd
--- /dev/null
+++ b/app/ui/global-loading.tsx
@@ -0,0 +1,46 @@
+import { useEffect, useRef, useState } from "react";
+import { useNavigation } from "@remix-run/react";
+import cx from "clsx";
+
+export function GlobalLoading() {
+ let transition = useNavigation();
+ let active = transition.state !== "idle";
+
+ let ref = useRef(null);
+ let [animating, setAnimating] = useState(false);
+
+ useEffect(() => {
+ if (!ref.current) return;
+
+ Promise.allSettled(
+ ref.current.getAnimations().map(({ finished }) => finished),
+ ).then(() => {
+ if (!active) setAnimating(false);
+ });
+
+ if (active) {
+ let id = setTimeout(() => setAnimating(true), 100);
+ return () => clearTimeout(id);
+ }
+ }, [active]);
+
+ return (
+
+ );
+}