summaryrefslogtreecommitdiff
path: root/jb-ui/src/components/ui/magic-card.tsx
diff options
context:
space:
mode:
authorMax Nanis2026-02-18 20:42:03 -0500
committerMax Nanis2026-02-18 20:42:03 -0500
commit3eaa56f0306ead818f64c3d99fc6d230d9b970a4 (patch)
tree9fecc2f1456e6321572e0e65f57106916df173e2 /jb-ui/src/components/ui/magic-card.tsx
downloadamt-jb-3eaa56f0306ead818f64c3d99fc6d230d9b970a4.tar.gz
amt-jb-3eaa56f0306ead818f64c3d99fc6d230d9b970a4.zip
HERE WE GO, HERE WE GO, HERE WE GO
Diffstat (limited to 'jb-ui/src/components/ui/magic-card.tsx')
-rw-r--r--jb-ui/src/components/ui/magic-card.tsx101
1 files changed, 101 insertions, 0 deletions
diff --git a/jb-ui/src/components/ui/magic-card.tsx b/jb-ui/src/components/ui/magic-card.tsx
new file mode 100644
index 0000000..e6503d8
--- /dev/null
+++ b/jb-ui/src/components/ui/magic-card.tsx
@@ -0,0 +1,101 @@
+import React, { useCallback, useEffect } from "react"
+import { motion, useMotionTemplate, useMotionValue } from "motion/react"
+
+import { cn } from "@/lib/utils"
+
+interface MagicCardProps {
+ children?: React.ReactNode
+ className?: string
+ gradientSize?: number
+ gradientColor?: string
+ gradientOpacity?: number
+ gradientFrom?: string
+ gradientTo?: string
+}
+
+export function MagicCard({
+ children,
+ className,
+ gradientSize = 200,
+ gradientColor = "#262626",
+ gradientOpacity = 0.8,
+ gradientFrom = "#9E7AFF",
+ gradientTo = "#FE8BBB",
+}: MagicCardProps) {
+ const mouseX = useMotionValue(-gradientSize)
+ const mouseY = useMotionValue(-gradientSize)
+ const reset = useCallback(() => {
+ mouseX.set(-gradientSize)
+ mouseY.set(-gradientSize)
+ }, [gradientSize, mouseX, mouseY])
+
+ const handlePointerMove = useCallback(
+ (e: React.PointerEvent<HTMLDivElement>) => {
+ const rect = e.currentTarget.getBoundingClientRect()
+ mouseX.set(e.clientX - rect.left)
+ mouseY.set(e.clientY - rect.top)
+ },
+ [mouseX, mouseY]
+ )
+
+ useEffect(() => {
+ reset()
+ }, [reset])
+
+ useEffect(() => {
+ const handleGlobalPointerOut = (e: PointerEvent) => {
+ if (!e.relatedTarget) {
+ reset()
+ }
+ }
+
+ const handleVisibility = () => {
+ if (document.visibilityState !== "visible") {
+ reset()
+ }
+ }
+
+ window.addEventListener("pointerout", handleGlobalPointerOut)
+ window.addEventListener("blur", reset)
+ document.addEventListener("visibilitychange", handleVisibility)
+
+ return () => {
+ window.removeEventListener("pointerout", handleGlobalPointerOut)
+ window.removeEventListener("blur", reset)
+ document.removeEventListener("visibilitychange", handleVisibility)
+ }
+ }, [reset])
+
+ return (
+ <div
+ className={cn("group relative rounded-[inherit]", className)}
+ onPointerMove={handlePointerMove}
+ onPointerLeave={reset}
+ onPointerEnter={reset}
+ >
+ <motion.div
+ className="bg-border pointer-events-none absolute inset-0 rounded-[inherit] duration-300 group-hover:opacity-100"
+ style={{
+ background: useMotionTemplate`
+ radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px,
+ ${gradientFrom},
+ ${gradientTo},
+ var(--border) 100%
+ )
+ `,
+ }}
+ />
+ <div className="bg-background absolute inset-px rounded-[inherit]" />
+ <motion.div
+ className="pointer-events-none absolute inset-px rounded-[inherit] opacity-0 transition-opacity duration-300 group-hover:opacity-100"
+ style={{
+ background: useMotionTemplate`
+ radial-gradient(${gradientSize}px circle at ${mouseX}px ${mouseY}px, ${gradientColor}, transparent 100%)
+ `,
+ opacity: gradientOpacity,
+ }}
+ />
+ <div className="relative">{children}</div>
+ </div>
+ )
+}