import Lottie from "lottie-react"; import React from "react"; import { customColors } from "@sparkle/lib/colors"; import animColor from "@sparkle/lottie/spinnerColor"; import animColorLG from "@sparkle/lottie/spinnerColorLG"; import animColorXS from "@sparkle/lottie/spinnerColorXS"; import animDark from "@sparkle/lottie/spinnerDark"; import animDarkLG from "@sparkle/lottie/spinnerDarkLG"; import animDarkXS from "@sparkle/lottie/spinnerDarkXS"; import animLight from "@sparkle/lottie/spinnerLight"; import animLightLG from "@sparkle/lottie/spinnerLightLG"; import animLightXS from "@sparkle/lottie/spinnerLightXS"; type SpinnerSizeType = (typeof SPINNER_SIZES)[number]; const SPINNER_SIZES = ["xs", "sm", "md", "lg", "xl", "2xl"] as const; type SpinnerVariant = | "mono" | "revert" | "light" | "dark" | "color" | SpinnerVariantType; export interface SpinnerProps { size?: SpinnerSizeType; variant?: SpinnerVariant; } // Generate all possible color-shade combinations const colorVariants = Object.entries(customColors).flatMap(([color, shades]) => Object.keys(shades).map((shade) => `${color}${shade}` as const) ); const SPINNER_VARIANTS = ["color", ...colorVariants] as const; type SpinnerVariantType = (typeof SPINNER_VARIANTS)[number]; const pxSizeClasses: Record = { xs: "16", sm: "20", md: "24", lg: "32", xl: "128", "2xl": "192", }; type LottieColorType = [number, number, number, number]; // Convert hex to RGB array [r, g, b, a] const hexToRgba = (hex: string): [number, number, number, number] => { const r = parseInt(hex.slice(1, 3), 16) / 255; const g = parseInt(hex.slice(3, 5), 16) / 255; const b = parseInt(hex.slice(5, 7), 16) / 255; return [r, g, b, 1]; }; const colors: Record, LottieColorType> = { ...Object.fromEntries( colorVariants.map((variant) => { const color = variant.match(/[a-z]+/)?.[0] as keyof typeof customColors; const shade = variant.match( /\d+/ )?.[0] as unknown as keyof (typeof customColors)[typeof color]; return [variant, hexToRgba(customColors[color][shade])]; }) ), }; const isColorArray = (arr: unknown): arr is LottieColorType => { return ( Array.isArray(arr) && arr.length === 4 && arr.every((n) => typeof n === "number") ); }; // Due to the dynamic nature of Lottie, we use 'any' for the input object. // This function recursively replaces color arrays within the Lottie animation object. // eslint-disable-next-line @typescript-eslint/no-explicit-any const replaceColors = (obj: any, newColor: LottieColorType): any => { if (Array.isArray(obj)) { return obj.map((item) => replaceColors(item, newColor)); } else if (obj !== null && typeof obj === "object") { for (const key in obj) { if (isColorArray(obj[key])) { obj[key] = newColor; } else { obj[key] = replaceColors(obj[key], newColor); } } } return obj; }; const Spinner: React.FC = ({ size = "md", variant = "mono" }) => { const fullSize = parseInt(pxSizeClasses[size], 10); // Handle custom color variants if ( variant !== "revert" && variant !== "mono" && variant !== "color" && variant !== "light" && variant !== "dark" ) { let anim; switch (size) { case "xs": case "sm": case "md": anim = animLightXS; break; case "xl": case "2xl": anim = animLightLG; break; default: anim = animLight; } const animationData = replaceColors( JSON.parse(JSON.stringify(anim)), colors[variant] ); return ( ); } // Handle color variant if (variant === "color") { let anim; switch (size) { case "xs": case "sm": case "md": anim = animColorXS; break; case "xl": case "2xl": anim = animColorLG; break; default: anim = animColor; } return ( ); } if (variant === "light") { let anim; switch (size) { case "xs": case "sm": case "md": anim = animLightXS; break; case "xl": case "2xl": anim = animLightLG; break; default: anim = animLight; } return ( ); } if (variant === "dark") { let anim; switch (size) { case "xs": case "sm": case "md": anim = animDarkXS; break; case "xl": case "2xl": anim = animDarkLG; break; default: anim = animDark; } return ( ); } // Handle mono variant (default) let lightAnim; let darkAnim; switch (size) { case "xs": case "sm": case "md": lightAnim = animLightXS; darkAnim = animDarkXS; break; case "xl": case "2xl": lightAnim = animLightLG; darkAnim = animDarkLG; break; default: lightAnim = animLight; darkAnim = animDark; } return ( <> ); }; export default Spinner;