MedASR-Bench / src /components /ui /Card.tsx
ngoan
MedASR Bench: full platform + Hugging Face Docker Space packaging
d70f132
Raw
History Blame Contribute Delete
5.23 kB
"use client";
import clsx from "clsx";
import type { ReactNode } from "react";
import { useCallback, useRef } from "react";
import { motion } from "framer-motion";
export interface CardProps {
children: ReactNode;
className?: string;
/** Enable spotlight hover effect */
spotlight?: boolean;
/** Glass morphism intensity */
glass?: "none" | "subtle" | "medium" | "strong";
/** Depth level for layered shadows */
depth?: 0 | 1 | 2 | 3;
/** Hover lift effect */
hoverLift?: boolean;
/** Animated entrance */
animate?: boolean;
/** Entrance animation delay */
delay?: number;
}
/**
* Card - Translucent surface with hairline border.
* Features spotlight illumination, glass morphism, and depth effects.
*/
export default function Card({
children,
className,
spotlight = true,
glass = "none",
depth = 1,
hoverLift = true,
animate = false,
delay = 0,
}: CardProps) {
const cardRef = useRef<HTMLDivElement>(null);
const handleMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!cardRef.current || !spotlight) return;
const rect = cardRef.current.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
cardRef.current.style.setProperty("--mouse-x", `${x}%`);
cardRef.current.style.setProperty("--mouse-y", `${y}%`);
}, [spotlight]);
const glassClasses = {
none: "",
subtle: "bg-surface/60 backdrop-blur-sm",
medium: "bg-surface/80 backdrop-blur-md",
strong: "bg-surface/95 backdrop-blur-lg",
};
const depthClasses = {
0: "",
1: "depth-1",
2: "depth-2",
3: "depth-3",
};
const cardContent = (
<div
ref={cardRef}
onMouseMove={spotlight ? handleMouseMove : undefined}
className={clsx(
"hairline relative overflow-hidden rounded-sm",
glass === "none" && "bg-surface",
glassClasses[glass],
depthClasses[depth],
spotlight && "spotlight-card",
hoverLift && "hover-lift",
className
)}
>
{/* Inner glow at top edge */}
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-accent/10 to-transparent" />
{/* Content */}
<div className="relative z-10">{children}</div>
</div>
);
if (animate) {
return (
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: 0.4,
delay,
ease: [0.22, 1, 0.36, 1],
}}
>
{cardContent}
</motion.div>
);
}
return cardContent;
}
/**
* InteractiveCard - Card with enhanced hover/tap interactions
*/
export interface InteractiveCardProps extends CardProps {
onClick?: () => void;
href?: string;
}
export function InteractiveCard({
children,
className,
spotlight = true,
glass = "none",
depth = 1,
hoverLift = true,
onClick,
href,
}: InteractiveCardProps) {
// Use state to track mouse position for spotlight effect
const handleMouseMove = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!spotlight) return;
const rect = e.currentTarget.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 100;
const y = ((e.clientY - rect.top) / rect.height) * 100;
e.currentTarget.style.setProperty("--mouse-x", `${x}%`);
e.currentTarget.style.setProperty("--mouse-y", `${y}%`);
}, [spotlight]);
const glassClasses = {
none: "",
subtle: "bg-surface/60 backdrop-blur-sm",
medium: "bg-surface/80 backdrop-blur-md",
strong: "bg-surface/95 backdrop-blur-lg",
};
const depthClasses = {
0: "",
1: "depth-1",
2: "depth-2",
3: "depth-3",
};
// Use motion.div for consistent typing, with conditional href on anchor
return (
<motion.div
onMouseMove={spotlight ? handleMouseMove : undefined}
whileHover={hoverLift ? { y: -2, transition: { duration: 0.2, ease: [0.22, 1, 0.36, 1] } } : undefined}
whileTap={(onClick || href) ? { scale: 0.99, transition: { duration: 0.1 } } : undefined}
className={clsx(
"hairline group relative block w-full overflow-hidden rounded-sm text-left",
glass === "none" && "bg-surface",
glassClasses[glass],
depthClasses[depth],
spotlight && "spotlight-card",
(onClick || href) && "cursor-pointer",
className
)}
onClick={onClick}
>
{/* Inner glow */}
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-accent/15 to-transparent" />
{/* Hover accent line */}
<motion.div
className="pointer-events-none absolute inset-x-0 bottom-0 h-px origin-left bg-gradient-to-r from-transparent via-accent/30 to-transparent"
initial={{ scaleX: 0 }}
whileHover={{ scaleX: 1 }}
transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
/>
{/* Content */}
{href ? (
<a href={href} className="relative z-10 block">{children}</a>
) : (
<div className="relative z-10">{children}</div>
)}
</motion.div>
);
}