Files
livedash-node/components/magicui/pointer.tsx
Kaj Kowalski 5bfd762e55 fix: comprehensive TypeScript/build fixes and modernization
- Update tsconfig to ES2024 target and bundler moduleResolution
- Add dynamic imports for chart.js and recharts (bundle optimization)
- Consolidate 17 useState into useReducer in sessions page
- Fix 18 .js extension imports across lib files
- Add type declarations for @rapideditor/country-coder
- Fix platform user types (PlatformUserRole enum)
- Fix Calendar component prop types
- Centralize next-auth type augmentation
- Add force-dynamic to all API routes (prevent build-time prerender)
- Fix Prisma JSON null handling with Prisma.DbNull
- Fix various type mismatches (SessionMessage, ImportRecord, etc.)
- Export ButtonProps from button component
- Update next-themes import path
- Replace JSX.Element with React.ReactElement
- Remove obsolete debug scripts and pnpm lockfile
- Downgrade eslint to v8 for next compatibility
2026-01-20 07:28:10 +01:00

122 lines
3.4 KiB
TypeScript

"use client";
import {
AnimatePresence,
type HTMLMotionProps,
motion,
useMotionValue,
} from "motion/react";
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
interface PointerProps extends Omit<HTMLMotionProps<"div">, "ref"> {
children?: React.ReactNode;
}
/**
* A custom pointer component that displays an animated cursor.
* Add this as a child to any component to enable a custom pointer when hovering.
* You can pass custom children to render as the pointer.
*
* @component
* @param {PointerProps} props - The component props
*/
export function Pointer({
className,
style,
children,
...props
}: PointerProps): React.ReactElement {
const x = useMotionValue(0);
const y = useMotionValue(0);
const [isActive, setIsActive] = useState<boolean>(false);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (typeof window !== "undefined" && containerRef.current) {
// Get the parent element directly from the ref
const parentElement = containerRef.current.parentElement;
if (parentElement) {
// Add cursor-none to parent
parentElement.style.cursor = "none";
// Add event listeners to parent
const handleMouseMove = (e: MouseEvent) => {
x.set(e.clientX);
y.set(e.clientY);
};
const handleMouseEnter = () => {
setIsActive(true);
};
const handleMouseLeave = () => {
setIsActive(false);
};
parentElement.addEventListener("mousemove", handleMouseMove);
parentElement.addEventListener("mouseenter", handleMouseEnter);
parentElement.addEventListener("mouseleave", handleMouseLeave);
return () => {
parentElement.style.cursor = "";
parentElement.removeEventListener("mousemove", handleMouseMove);
parentElement.removeEventListener("mouseenter", handleMouseEnter);
parentElement.removeEventListener("mouseleave", handleMouseLeave);
};
}
}
}, [x, y]);
return (
<>
<div ref={containerRef} />
<AnimatePresence>
{isActive && (
<motion.div
className="transform-[translate(-50%,-50%)] pointer-events-none fixed z-50"
style={{
top: y,
left: x,
...style,
}}
initial={{
scale: 0,
opacity: 0,
}}
animate={{
scale: 1,
opacity: 1,
}}
exit={{
scale: 0,
opacity: 0,
}}
{...props}
>
{children || (
<svg
stroke="currentColor"
fill="currentColor"
strokeWidth="1"
viewBox="0 0 16 16"
height="24"
width="24"
xmlns="http://www.w3.org/2000/svg"
className={cn(
"rotate-[-70deg] stroke-white text-black",
className
)}
>
<title>Mouse pointer</title>
<path d="M14.082 2.182a.5.5 0 0 1 .103.557L8.528 15.467a.5.5 0 0 1-.917-.007L5.57 10.694.803 8.652a.5.5 0 0 1-.006-.916l12.728-5.657a.5.5 0 0 1 .556.103z" />
</svg>
)}
</motion.div>
)}
</AnimatePresence>
</>
);
}