From 0e9891b5a219ef2b45426c7e0645e9425f159c98 Mon Sep 17 00:00:00 2001 From: Yehoshua Sandler Date: Wed, 30 Apr 2025 14:32:21 -0500 Subject: [PATCH] feat: home page hero enter button --- components/motion-primitives/magnetic.tsx | 112 ++++++++++++++++++++++ public/images/down.svg | 1 + src/app/(frontend)/page.tsx | 66 ++----------- src/components/HomeHero.tsx | 101 +++++++++++++++++++ 4 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 components/motion-primitives/magnetic.tsx create mode 100644 public/images/down.svg create mode 100644 src/components/HomeHero.tsx diff --git a/components/motion-primitives/magnetic.tsx b/components/motion-primitives/magnetic.tsx new file mode 100644 index 0000000..f9a04c6 --- /dev/null +++ b/components/motion-primitives/magnetic.tsx @@ -0,0 +1,112 @@ +'use client'; + +import React, { useState, useEffect, useRef } from 'react'; +import { + motion, + useMotionValue, + useSpring, + type SpringOptions, +} from 'motion/react'; + +const SPRING_CONFIG = { stiffness: 26.7, damping: 4.1, mass: 0.2 }; + +export type MagneticProps = { + children: React.ReactNode; + intensity?: number; + range?: number; + actionArea?: 'self' | 'parent' | 'global'; + springOptions?: SpringOptions; +}; + +export function Magnetic({ + children, + intensity = 0.6, + range = 100, + actionArea = 'self', + springOptions = SPRING_CONFIG, +}: MagneticProps) { + const [isHovered, setIsHovered] = useState(false); + const ref = useRef(null); + + const x = useMotionValue(0); + const y = useMotionValue(0); + + const springX = useSpring(x, springOptions); + const springY = useSpring(y, springOptions); + + useEffect(() => { + const calculateDistance = (e: MouseEvent) => { + if (ref.current) { + const rect = ref.current.getBoundingClientRect(); + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + const distanceX = e.clientX - centerX; + const distanceY = e.clientY - centerY; + + const absoluteDistance = Math.sqrt(distanceX ** 2 + distanceY ** 2); + + if (isHovered && absoluteDistance <= range) { + const scale = 1 - absoluteDistance / range; + x.set(distanceX * intensity * scale); + y.set(distanceY * intensity * scale); + } else { + x.set(0); + y.set(0); + } + } + }; + + document.addEventListener('mousemove', calculateDistance); + + return () => { + document.removeEventListener('mousemove', calculateDistance); + }; + }, [ref, isHovered, intensity, range]); + + useEffect(() => { + if (actionArea === 'parent' && ref.current?.parentElement) { + const parent = ref.current.parentElement; + + const handleParentEnter = () => setIsHovered(true); + const handleParentLeave = () => setIsHovered(false); + + parent.addEventListener('mouseenter', handleParentEnter); + parent.addEventListener('mouseleave', handleParentLeave); + + return () => { + parent.removeEventListener('mouseenter', handleParentEnter); + parent.removeEventListener('mouseleave', handleParentLeave); + }; + } else if (actionArea === 'global') { + setIsHovered(true); + } + }, [actionArea]); + + const handleMouseEnter = () => { + if (actionArea === 'self') { + setIsHovered(true); + } + }; + + const handleMouseLeave = () => { + if (actionArea === 'self') { + setIsHovered(false); + x.set(0); + y.set(0); + } + }; + + return ( + + {children} + + ); +} diff --git a/public/images/down.svg b/public/images/down.svg new file mode 100644 index 0000000..7a277fc --- /dev/null +++ b/public/images/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index 4189bc3..d881da2 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -10,6 +10,10 @@ import { TextShimmer } from '@/components/ui/text-shimmer' import { LoginForm } from '@/components/login-form' import SearchBooks from '@/components/Search/SearchBooks' import Manage from '@/components/Manage/Manage' +import { Magnetic } from 'components/motion-primitives/magnetic' +import { Button } from '@/components/ui/button' +import Image from 'next/image' +import HomeHero from '@/components/HomeHero' export default async function HomePage() { const headers = await getHeaders() @@ -89,68 +93,10 @@ export default async function HomePage() { return (
-
- - -