Files
livedash-node/components/magicui/pointer.tsx
Kaj Kowalski ef71c9c06e feat: implement User Management dark mode with comprehensive testing
## Dark Mode Implementation
- Convert User Management page to shadcn/ui components for proper theming
- Replace hardcoded colors with CSS variables for dark/light mode support
- Add proper test attributes and accessibility improvements
- Fix loading state management and null safety issues

## Test Suite Implementation
- Add comprehensive User Management page tests (18 tests passing)
- Add format-enums utility tests (24 tests passing)
- Add integration test infrastructure with proper mocking
- Add accessibility test framework with jest-axe integration
- Add keyboard navigation test structure
- Fix test environment configuration for React components

## Code Quality Improvements
- Fix all ESLint warnings and errors
- Add null safety for users array (.length → ?.length || 0)
- Add proper form role attribute for accessibility
- Fix TypeScript interface issues in magic UI components
- Improve component error handling and user experience

## Technical Infrastructure
- Add jest-dom and node-mocks-http testing dependencies
- Configure jsdom environment for React component testing
- Add window.matchMedia mock for theme provider compatibility
- Fix auth test mocking and database test configuration

Result: Core functionality working with 42/44 critical tests passing
All dark mode theming, user management, and utility functions verified
2025-06-28 06:53:14 +02:00

121 lines
3.3 KiB
TypeScript

"use client";
import { cn } from "@/lib/utils";
import {
AnimatePresence,
HTMLMotionProps,
motion,
useMotionValue,
} from "motion/react";
import { useEffect, useRef, useState } from "react";
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): JSX.Element {
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
)}
>
<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>
</>
);
}