Share Your Vision
Tell us about your business goals and the website you've always dreamed of. We'll collaborate to bring your ideas to life.
Tell us about your business goals and the website you've always dreamed of. We'll collaborate to bring your ideas to life.
Plan and build out your dream vacation to Mount Elbrus with the Trip Explor.
npm i clsx tailwind-merge framer-motion
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
interface HoverFacingCardProps extends React.HTMLAttributes<HTMLDivElement> {
onClick?: () => void;
}
export const HoverFacingCard: React.FC<HoverFacingCardProps> = ({ children, className, onClick, ...props }) => {
const cardRef: RefObject<HTMLDivElement> = useRef(null);
const [x, setX] = useState(50);
const [y, setY] = useState(50);
const reset = (card: any) => {
setX(50);
setY(50);
if (card) {
card.setAttribute("data-x", "50");
card.setAttribute("data-y", "50");
card.style.setProperty("--x", "50");
card.style.setProperty("--y", "50");
}
};
const handleMouseMove = (
e: { clientX: number; clientY: number },
data: { top: number; left: number; bottom: number; right: number; width: number; height: number },
card: any
) => {
if (
card &&
e.clientX + 20 >= data.left &&
e.clientX - 20 <= data.right &&
e.clientY + 20 >= data.top &&
e.clientY - 20 <= data.bottom
) {
const newX = Math.round((100 / data.width) * (e.clientX - data.left));
const newY = Math.round((100 / data.height) * (e.clientY - data.top));
setX(newX);
setY(newY);
card.setAttribute("data-x", newX.toString());
card.setAttribute("data-y", newY.toString());
card.style.setProperty("--x", newX.toString());
card.style.setProperty("--y", newY.toString());
} else {
reset(card);
}
};
useEffect(() => {
const card = cardRef.current;
if (card) {
const rect = card.getBoundingClientRect();
const data = {
top: rect.top,
left: rect.left,
bottom: rect.top + rect.height,
right: rect.left + rect.width,
width: rect.width,
height: rect.height
};
const handleMouseOut = () => reset(card);
const handleMouseMoveWithParams = (e: any) => handleMouseMove(e, data, card);
card.addEventListener("mousemove", handleMouseMoveWithParams);
card.addEventListener("mouseout", handleMouseOut);
return () => {
card.removeEventListener("mousemove", handleMouseMoveWithParams);
card.removeEventListener("mouseout", handleMouseOut);
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<article
ref={cardRef}
className={cn(style.card, className)}
onClick={onClick}
style={{ ["--x" as any]: x, ["--y" as any]: y }}
data-x={x}
data-y={y}
{...props}
>
{children}
</article>
);
};
interface HoverFacingCardTitleProps extends React.HTMLAttributes<HTMLDivElement> {}
export const HoverFacingCardTitle: React.FC<HoverFacingCardTitleProps> = ({ children, className, ...props }) => {
return (
<h2 data-content={children} className={cn(style.content, className)} {...props}>
{children}
</h2>
);
};
interface HoverFacingCardDescriptionProps extends React.HTMLAttributes<HTMLDivElement> {}
export const HoverFacingCardDescription: React.FC<HoverFacingCardDescriptionProps> = ({ children, className, ...props }) => {
return (
<p data-content={children} className={cn(style.content, className)} {...props}>
{children}
</p>
);
};