diff --git a/src/app/scripts/_components/ScriptPage.tsx b/src/app/scripts/_components/ScriptPage.tsx
index e9aa17e..bfdb2c9 100644
--- a/src/app/scripts/_components/ScriptPage.tsx
+++ b/src/app/scripts/_components/ScriptPage.tsx
@@ -2,6 +2,7 @@
import ScriptBrowser from "@/app/scripts/_components/ScriptBrowser";
import ScriptItem from "@/app/scripts/_components/ScriptItem";
+import { Spinner } from "@/components/ui/spinner";
import { Category } from "@/lib/types";
import { Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
@@ -56,7 +57,7 @@ export default function ScriptPage() {
return (
);
diff --git a/src/components/ui/spinner.tsx b/src/components/ui/spinner.tsx
new file mode 100644
index 0000000..184ba3d
--- /dev/null
+++ b/src/components/ui/spinner.tsx
@@ -0,0 +1,67 @@
+import { cn } from "@/lib/utils";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+import * as React from "react";
+
+const spinnerVariants = cva("relative block opacity-[0.65]", {
+ variants: {
+ size: {
+ sm: "w-4 h-4",
+ md: "w-6 h-6",
+ lg: "w-8 h-8",
+ },
+ },
+ defaultVariants: {
+ size: "sm",
+ },
+});
+
+export interface SpinnerProps
+ extends React.HTMLAttributes,
+ VariantProps {
+ loading?: boolean;
+ asChild?: boolean;
+}
+
+const Spinner = React.forwardRef(
+ ({ className, size, loading = true, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "span";
+
+ const [bgColorClass, filteredClassName] = React.useMemo(() => {
+ const bgClass = className?.match(/(?:dark:bg-|bg-)S+/g) || [];
+ const filteredClasses = className
+ ?.replace(/(?:dark:bg-|bg-)S+/g, "")
+ .trim();
+ return [bgClass, filteredClasses];
+ }, [className]);
+
+ if (!loading) return null;
+
+ return (
+
+ {Array.from({ length: 8 }).map((_, i) => (
+
+
+
+ ))}
+
+ );
+ },
+);
+
+Spinner.displayName = "Spinner";
+
+export { Spinner };
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 0a9bddc..b89e59e 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -126,12 +126,17 @@ const config = {
transform: "translateY(-50%)",
},
},
+ "spinner-leaf-fade": {
+ "0%, 100%": { opacity: "0" },
+ "50%": { opacity: "1" },
+ },
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
shine: "shine 8s ease-in-out infinite",
gradient: "gradient 8s linear infinite",
+ "spinner-leaf-fade": "spinner-leaf-fade 800ms linear infinite",
},
},
},