mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
feat: add HoverBorderGradient effect to key action buttons
Apply animated gradient border effect (Aceternity UI) to: - Dashboard "Connect Devices" button - Dashboard "Activate Trial" buttons (free and paid variants) - Subscription page "Connect Devices" button
This commit is contained in:
93
src/components/ui/hover-border-gradient.tsx
Normal file
93
src/components/ui/hover-border-gradient.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
'use client';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
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.2% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)',
|
||||
};
|
||||
|
||||
const highlight =
|
||||
'radial-gradient(75% 181.16% 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, duration]);
|
||||
|
||||
return (
|
||||
<Tag
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
className={cn(
|
||||
'relative flex content-center items-center justify-center overflow-visible rounded-xl border-none bg-black/20 p-px transition duration-500 hover:bg-black/10',
|
||||
containerClassName,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'z-10 w-auto rounded-[inherit] bg-dark-800 px-4 py-2.5 text-sm font-medium text-white',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<motion.div
|
||||
className="absolute inset-0 z-0 flex-none overflow-hidden rounded-[inherit]"
|
||||
style={{
|
||||
filter: 'blur(2px)',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
initial={{ background: movingMap[direction] }}
|
||||
animate={{
|
||||
background: hovered ? [movingMap[direction], highlight] : movingMap[direction],
|
||||
}}
|
||||
transition={{ ease: 'linear', duration: duration ?? 1 }}
|
||||
/>
|
||||
<div className="absolute inset-[2px] z-[1] flex-none rounded-[10px] bg-dark-800" />
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user