Spaces:
Sleeping
Sleeping
| import * as React from "react" | |
| import { cn } from "@/lib/utils" | |
| interface HoverButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { | |
| children: React.ReactNode | |
| } | |
| const HoverButton = React.forwardRef<HTMLButtonElement, HoverButtonProps>( | |
| ({ className, children, ...props }, ref) => { | |
| const buttonRef = React.useRef<HTMLButtonElement>(null) | |
| const [isListening, setIsListening] = React.useState(false) | |
| const [circles, setCircles] = React.useState<Array<{ | |
| id: number | |
| x: number | |
| y: number | |
| color: string | |
| fadeState: "in" | "out" | null | |
| }>>([]) | |
| const lastAddedRef = React.useRef(0) | |
| const createCircle = React.useCallback((x: number, y: number) => { | |
| const buttonWidth = buttonRef.current?.offsetWidth || 0 | |
| const xPos = x / buttonWidth | |
| const color = `linear-gradient(to right, var(--circle-start) ${xPos * 100}%, var(--circle-end) ${ | |
| xPos * 100 | |
| }%)` | |
| setCircles((prev) => [ | |
| ...prev, | |
| { id: Date.now(), x, y, color, fadeState: null }, | |
| ]) | |
| }, []) | |
| const handlePointerMove = React.useCallback( | |
| (event: React.PointerEvent<HTMLButtonElement>) => { | |
| if (!isListening) return | |
| const currentTime = Date.now() | |
| if (currentTime - lastAddedRef.current > 100) { | |
| lastAddedRef.current = currentTime | |
| const rect = event.currentTarget.getBoundingClientRect() | |
| const x = event.clientX - rect.left | |
| const y = event.clientY - rect.top | |
| createCircle(x, y) | |
| } | |
| }, | |
| [isListening, createCircle] | |
| ) | |
| const handlePointerEnter = React.useCallback(() => { | |
| setIsListening(true) | |
| }, []) | |
| const handlePointerLeave = React.useCallback(() => { | |
| setIsListening(false) | |
| }, []) | |
| React.useEffect(() => { | |
| circles.forEach((circle) => { | |
| if (!circle.fadeState) { | |
| setTimeout(() => { | |
| setCircles((prev) => | |
| prev.map((c) => | |
| c.id === circle.id ? { ...c, fadeState: "in" } : c | |
| ) | |
| ) | |
| }, 0) | |
| setTimeout(() => { | |
| setCircles((prev) => | |
| prev.map((c) => | |
| c.id === circle.id ? { ...c, fadeState: "out" } : c | |
| ) | |
| ) | |
| }, 1000) | |
| setTimeout(() => { | |
| setCircles((prev) => prev.filter((c) => c.id !== circle.id)) | |
| }, 2200) | |
| } | |
| }) | |
| }, [circles]) | |
| return ( | |
| <button | |
| ref={buttonRef} | |
| className={cn( | |
| "relative isolate px-4 sm:px-6 lg:px-8 py-2.5 sm:py-3 rounded-2xl sm:rounded-3xl", | |
| "text-foreground font-medium text-sm sm:text-base leading-6", | |
| "backdrop-blur-lg bg-[rgba(43,55,80,0.1)]", | |
| "cursor-pointer overflow-hidden touch-target", | |
| "min-h-[44px] min-w-[44px]", | |
| "before:content-[''] before:absolute before:inset-0", | |
| "before:rounded-[inherit] before:pointer-events-none", | |
| "before:z-[1]", | |
| "before:shadow-[inset_0_0_0_1px_rgba(170,202,255,0.2),inset_0_0_16px_0_rgba(170,202,255,0.1),inset_0_-3px_12px_0_rgba(170,202,255,0.15),0_1px_3px_0_rgba(0,0,0,0.50),0_4px_12px_0_rgba(0,0,0,0.45)]", | |
| "before:mix-blend-multiply before:transition-transform before:duration-300", | |
| "active:before:scale-[0.975] hover:scale-105 transition-all duration-200", | |
| className | |
| )} | |
| onPointerMove={handlePointerMove} | |
| onPointerEnter={handlePointerEnter} | |
| onPointerLeave={handlePointerLeave} | |
| {...props} | |
| style={{ | |
| "--circle-start": "var(--tw-gradient-from, #a0d9f8)", | |
| "--circle-end": "var(--tw-gradient-to, #3a5bbf)", | |
| }} | |
| > | |
| {circles.map(({ id, x, y, color, fadeState }) => ( | |
| <div | |
| key={id} | |
| className={cn( | |
| "absolute w-2 h-2 sm:w-3 sm:h-3 -translate-x-1/2 -translate-y-1/2 rounded-full", | |
| "blur-sm sm:blur-lg pointer-events-none z-[-1] transition-opacity duration-300", | |
| fadeState === "in" && "opacity-60 sm:opacity-75", | |
| fadeState === "out" && "opacity-0 duration-1000", | |
| !fadeState && "opacity-0" | |
| )} | |
| style={{ | |
| left: x, | |
| top: y, | |
| background: color, | |
| }} | |
| /> | |
| ))} | |
| {children} | |
| </button> | |
| ) | |
| } | |
| ) | |
| HoverButton.displayName = "HoverButton" | |
| export { HoverButton } |