"use client"; import React from "react"; import { motion, type Variants } from "framer-motion"; // Import Variants type import { cn } from "../../lib/utils"; export interface ShinyTextProps { /** Text content to display */ children: React.ReactNode; /** Disable the shiny animation */ disabled?: boolean; /** Animation speed in seconds */ speed?: number; /** Custom className */ className?: string; /** Text size variant */ size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl"; /** Font weight */ weight?: "normal" | "medium" | "semibold" | "bold" | "extrabold"; /** Base text color */ baseColor?: string; /** Shine effect color */ shineColor?: string; /** Shine effect intensity (0-1) */ intensity?: number; /** Animation direction */ direction?: "left-to-right" | "right-to-left" | "top-to-bottom" | "bottom-to-top"; /** Shine effect width percentage */ shineWidth?: number; /** Delay before animation starts in seconds */ delay?: number; /** Animation repeat behavior */ repeat?: number | "infinite"; /** Pause animation on hover */ pauseOnHover?: boolean; /** Gradient type */ gradientType?: "linear" | "radial"; } const sizeClasses = { xs: "text-xs", sm: "text-sm", base: "text-base", lg: "text-lg", xl: "text-xl", "2xl": "text-2xl", "3xl": "text-3xl", "4xl": "text-4xl", }; const weightClasses = { normal: "font-normal", medium: "font-medium", semibold: "font-semibold", bold: "font-bold", extrabold: "font-extrabold", }; const directionConfig = { "left-to-right": { backgroundPosition: ["100% 0%", "-100% 0%"], backgroundSize: "200% 100%", }, "right-to-left": { backgroundPosition: ["-100% 0%", "100% 0%"], backgroundSize: "200% 100%", }, "top-to-bottom": { backgroundPosition: ["0% 100%", "0% -100%"], backgroundSize: "100% 200%", }, "bottom-to-top": { backgroundPosition: ["0% -100%", "0% 100%"], backgroundSize: "100% 200%", }, }; export function ShinyText({ children, disabled = false, speed = 3, className, size = "base", weight = "medium", baseColor, shineColor, intensity = 1, direction = "left-to-right", shineWidth = 0, delay = 0, repeat = "infinite", pauseOnHover = false, gradientType = "linear", }: ShinyTextProps) { const config = directionConfig[direction]; const gradientDirection = direction === "left-to-right" || direction === "right-to-left" ? "90deg" : direction === "top-to-bottom" ? "180deg" : "0deg"; // Default colors based on theme const defaultBaseColor = "hsl(var(--foreground)/20)"; const defaultShineColor = "hsl(var(--primary)/20)"; const finalBaseColor = baseColor || defaultBaseColor; const finalShineColor = shineColor || defaultShineColor; const createGradient = () => { const transparentStartPos = Math.max(0, (50 - shineWidth / 2)); const transparentEndPos = Math.min(100, (50 + shineWidth / 2)); const shineStart = `${finalShineColor} ${transparentStartPos}%`; const shineEnd = `${finalShineColor} ${transparentEndPos}%`; return gradientType === "linear" ? `linear-gradient(${gradientDirection}, ${finalBaseColor}, transparent ${transparentStartPos - 5}%, ${shineStart}, ${shineEnd}, transparent ${transparentEndPos + 5}%, ${finalBaseColor})` : `radial-gradient(ellipse at center, ${finalShineColor} ${intensity * 100}%, transparent)`; }; // Define the animate state structure consistently const animationVariants: Variants = { // Explicitly type as Variants initial: { backgroundPosition: config.backgroundPosition[0], }, animate: disabled ? { // When disabled, snap to initial position with no animation backgroundPosition: config.backgroundPosition[0], transition: { duration: 0, delay: 0, repeat: 0, // Explicitly define repeat and ease for consistent type ease: "linear", // Even if not used, keeps type consistent }, } : { backgroundPosition: config.backgroundPosition[1], transition: { duration: speed, delay, repeat: typeof repeat === "number" ? repeat : Infinity, ease: "linear", }, }, hover: pauseOnHover ? { // Note: `animationPlayState` is a CSS property, Framer Motion variants // primarily animate numerical/string values. To truly pause a Framer Motion // animation, you'd typically use `useAnimationControls` and call `stop()`. // However, if this is a background CSS animation being controlled by Framer Motion's // `animate` prop, this might have an indirect effect or be ignored. // For a robust pause, consider a `useAnimationControls` hook. // Keeping it as is to preserve original logic for now, but be aware. } : {}, }; if (disabled) { return ( {children} ); } return ( {children} ); } export default ShinyText;