aboutsummaryrefslogtreecommitdiff
path: root/src/components/ui/hover-border-gradient.tsx
diff options
context:
space:
mode:
authorsotech117 <michael_foiani@brown.edu>2025-09-23 01:46:55 -0400
committersotech117 <michael_foiani@brown.edu>2025-09-23 01:46:55 -0400
commit3a712982307391ae1196f50e252cb37ed5f67ccb (patch)
tree279aa2d97198a08aef2eb1c80bc5008763eb98da /src/components/ui/hover-border-gradient.tsx
parentc5547dc1ba827a4256fbe1ed08bda61f8c660815 (diff)
add hero and get-started page
Diffstat (limited to 'src/components/ui/hover-border-gradient.tsx')
-rw-r--r--src/components/ui/hover-border-gradient.tsx100
1 files changed, 100 insertions, 0 deletions
diff --git a/src/components/ui/hover-border-gradient.tsx b/src/components/ui/hover-border-gradient.tsx
new file mode 100644
index 0000000..898f575
--- /dev/null
+++ b/src/components/ui/hover-border-gradient.tsx
@@ -0,0 +1,100 @@
+"use client";
+import React, { useState, useEffect, useRef } from "react";
+
+import { motion } from "motion/react";
+import { cn } from "@/lib/utils";
+
+type Direction = "TOP" | "LEFT" | "BOTTOM" | "RIGHT";
+
+export function HoverBorderGradient({
+ children,
+ containerClassName,
+ className,
+ as: Tag = "button",
+ duration = 1,
+ clockwise = true,
+ ...props
+}: React.PropsWithChildren<
+ {
+ as?: React.ElementType;
+ containerClassName?: string;
+ className?: string;
+ duration?: number;
+ clockwise?: boolean;
+ } & React.HTMLAttributes<HTMLElement>
+>) {
+ const [hovered, setHovered] = useState<boolean>(false);
+ const [direction, setDirection] = useState<Direction>("TOP");
+
+ const rotateDirection = (currentDirection: Direction): Direction => {
+ const directions: Direction[] = ["TOP", "LEFT", "BOTTOM", "RIGHT"];
+ const currentIndex = directions.indexOf(currentDirection);
+ const nextIndex = clockwise
+ ? (currentIndex - 1 + directions.length) % directions.length
+ : (currentIndex + 1) % directions.length;
+ return directions[nextIndex];
+ };
+
+ const movingMap: Record<Direction, string> = {
+ TOP: "radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ LEFT: "radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ BOTTOM:
+ "radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ RIGHT:
+ "radial-gradient(16.2% 41.199999999999996% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
+ };
+
+ const highlight =
+ "radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)";
+
+ useEffect(() => {
+ if (!hovered) {
+ const interval = setInterval(() => {
+ setDirection((prevState) => rotateDirection(prevState));
+ }, duration * 1000);
+ return () => clearInterval(interval);
+ }
+ }, [hovered]);
+ return (
+ <Tag
+ onMouseEnter={(event: React.MouseEvent<HTMLDivElement>) => {
+ setHovered(true);
+ }}
+ onMouseLeave={() => setHovered(false)}
+ className={cn(
+ "relative flex rounded-full border border-indigo-300/50 content-center bg-black/20 hover:bg-black/10 transition duration-500 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit",
+ containerClassName
+ )}
+ {...props}
+ >
+ <div
+ className={cn(
+ "w-auto text-white z-10 bg-black px-4 py-2 rounded-[inherit]",
+ className
+ )}
+ >
+ {children}
+ </div>
+ <motion.div
+ className={cn(
+ "flex-none inset-0 overflow-hidden absolute z-0 rounded-[inherit]"
+ )}
+ style={{
+ filter: "blur(2px)",
+ position: "absolute",
+ width: "100%",
+ height: "110%",
+ margin: "auto",
+ }}
+ initial={{ background: movingMap[direction] }}
+ animate={{
+ background: hovered
+ ? [movingMap[direction], highlight]
+ : movingMap[direction],
+ }}
+ transition={{ ease: "linear", duration: duration ?? 1 }}
+ />
+ <div className="bg-black absolute z-1 flex-none inset-[2px] rounded-[100px]" />
+ </Tag>
+ );
+}