"use client";
import { cn } from "@/lib/utils";
import React from "react";
interface LoaderProps {
size?: "sm" | "md" | "lg" | "xl" | string;
className?: string;
color?: string;
speed?: "default" | "slow" | "slowest" | "fast";
}
const sizeMap = {
sm: "20px",
md: "30px",
lg: "40px",
xl: "50px"
};
const dotSizeMap = {
sm: 10,
md: 12,
lg: 14,
xl: 16
};
const boxSizeMap = {
sm: 10,
md: 20,
lg: 30,
xl: 40
};
const speedMap = {
slow: "2s",
slowest: "3s",
fast: ".5s",
default: "1s"
};
const getSizeValue = (size: LoaderProps["size"]) => sizeMap[size as keyof typeof sizeMap] || size;
const getDotSizeValue = (size: LoaderProps["size"]) => dotSizeMap[size as keyof typeof dotSizeMap] || 4;
const getBoxSizeValue = (size: LoaderProps["size"]) => boxSizeMap[size as keyof typeof boxSizeMap] || 20;
const getSpeedValue = (speed: LoaderProps["speed"]) => speedMap[speed as keyof typeof speedMap] || "1s";
const DefaultLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div
className={cn("rounded-full border-2 border-solid", className)}
style={{
height: sizeValue,
width: sizeValue,
animation: `spin ${speedValue} linear infinite`,
borderLeftColor: color,
borderRightColor: color,
borderBottomColor: color,
borderTopColor: "transparent"
}}
></div>
);
};
const CrescentLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div className={cn("relative", className)}>
<div
style={{ height: sizeValue, width: sizeValue, borderRadius: "50%", border: "2px solid", borderColor: color, opacity: 0.25 }}
></div>
<div
style={{
position: "absolute",
left: 0,
top: 0,
height: sizeValue,
width: sizeValue,
animation: `spin ${speedValue} linear infinite`,
borderRadius: "50%",
borderTop: `2px solid ${color}`
}}
></div>
</div>
);
};
const ArrowsLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div className={className}>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 16 16"
style={{
height: sizeValue,
width: sizeValue,
animation: `spin ${speedValue} linear infinite`,
color
}}
>
<path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41zm-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9z" />
<path
fillRule="evenodd"
d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"
/>
</svg>
</div>
);
};
const DashedLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div
className={cn("rounded-full border-2 border-dashed", className)}
style={{
height: sizeValue,
width: sizeValue,
animation: `spin ${speedValue} linear infinite`,
borderColor: color
}}
></div>
);
};
const DoubleCrescentLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div
className={cn("rounded-full border-b-2 border-t-2", className)}
style={{
height: sizeValue,
width: sizeValue,
animation: `spin ${speedValue} linear infinite`,
borderColor: color
}}
></div>
);
};
const CrescentSharpLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div className={cn("relative", className)}>
<div
style={{ height: sizeValue, width: sizeValue, borderRadius: "50%", border: "2px solid", borderColor: color, opacity: 0.25 }}
></div>
<div
style={{
position: "absolute",
left: 0,
top: 0,
height: sizeValue,
width: sizeValue,
animation: `spin ${speedValue} linear infinite`,
borderRadius: "50%",
border: "2px solid transparent",
borderTopColor: color
}}
></div>
</div>
);
};
const LinesLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<>
<div
className={cn("spinner grid rounded-full", className)}
style={{
width: sizeValue,
height: sizeValue,
animation: `s3 ${speedValue} steps(12) infinite`
}}
></div>
<style jsx>{`
.spinner {
background:
linear-gradient(0deg, ${color}80 30%, #0000 0 70%, ${color}ff 0) 50%/8% 100%,
linear-gradient(90deg, ${color}50 30%, #0000 0 70%, ${color}bf 0) 50%/100% 8%;
background-repeat: no-repeat;
}
.spinner::before,
.spinner::after {
content: "";
grid-area: 1/1;
border-radius: 50%;
background: inherit;
opacity: 0.915;
transform: rotate(30deg);
}
.spinner::after {
opacity: 0.83;
transform: rotate(60deg);
}
@keyframes s3 {
100% {
transform: rotate(1turn);
}
}
`}</style>
</>
);
};
const CirclesLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div className={className}>
<div
className={`spinner grid rounded-full`}
style={{
animation: `s6 ${speedValue} steps(12) infinite`,
width: sizeValue,
height: sizeValue
}}
></div>
<style jsx>{`
.spinner {
-webkit-mask: conic-gradient(from 15deg, #0000, #000);
}
.spinner,
.spinner:before,
.spinner:after {
background:
radial-gradient(closest-side at 50% 12.5%, ${color} 96%, #0000) 50% 0/20% 80% repeat-y,
radial-gradient(closest-side at 12.5% 50%, ${color} 96%, #0000) 0 50%/80% 20% repeat-x;
}
.spinner:before,
.spinner:after {
content: "";
grid-area: 1/1;
transform: rotate(30deg);
}
.spinner:after {
transform: rotate(60deg);
}
@keyframes s6 {
100% {
transform: rotate(1turn);
}
}
`}</style>
</div>
);
};
const DotsLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const dotSizeValue = getDotSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div className={cn("flex items-center justify-center space-x-2", className)}>
{[0, 1, 2].map((index) => (
<div
key={index}
style={{
backgroundColor: color,
animation: `pulse ${speedValue} infinite`,
animationDelay: `${index * 0.3}s`,
width: `${dotSizeValue}px`,
height: `${dotSizeValue}px`,
borderRadius: "50%"
}}
></div>
))}
</div>
);
};
const BounceLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const dotSizeValue = getDotSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div className={cn("flex items-center justify-center space-x-2", className)}>
{[0, 1, 2].map((index) => (
<div
key={index}
style={{
backgroundColor: color,
animation: `bounce ${speedValue} infinite`,
animationDelay: `${index * 0.2}s`,
width: `${dotSizeValue}px`,
height: `${dotSizeValue}px`,
borderRadius: "50%"
}}
></div>
))}
</div>
);
};
const TriplesLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6", speed = "default" }) => {
const sizeValue = getSizeValue(size);
const speedValue = getSpeedValue(speed);
return (
<div className={className} style={{ height: sizeValue, width: sizeValue }}>
<svg viewBox="0 0 100 100">
<defs>
<filter id="shadow">
<feDropShadow dx="0" dy="0" stdDeviation="1.5" floodColor={color} />
</filter>
</defs>
<circle
style={{
fill: "transparent",
stroke: color,
strokeWidth: 7,
strokeLinecap: "round",
filter: "url(#shadow)",
animation: `triples ${speedValue} infinite`
}}
cx="50"
cy="50"
r="45"
/>
</svg>
<style jsx>{`
@keyframes triples {
0% {
stroke-dasharray: 1 98;
stroke-dashoffset: -105;
}
50% {
stroke-dasharray: 80 10;
stroke-dashoffset: -160;
}
100% {
stroke-dasharray: 1 98;
stroke-dashoffset: -300;
}
}
`}</style>
</div>
);
};
const BoxesLoader: React.FC<LoaderProps> = ({ size = "md", className, color = "#3bccf6" }) => {
const boxSizeValue = getBoxSizeValue(size);
return (
<div className={className}>
<div
className={"relative animate-[opposites_2.5s_ease-out_0s_infinite]"}
style={{
height: `${Number(boxSizeValue) * 2}px`,
width: `${Number(boxSizeValue) * 2}px`,
animation: `opposites 2.5s ease-out 0s infinite`
}}
>
<div
className={"absolute left-0 origin-top-right transform animate-[trbl_2.5s_ease-out_0s_infinite]"}
style={{
backgroundColor: color,
filter: "brightness(.9)",
top: `${boxSizeValue}px`,
left: 0,
height: `${boxSizeValue}px`,
width: `${boxSizeValue}px`
}}
></div>
<div
className={"absolute top-0 origin-bottom-left transform animate-[trbl_2.5s_ease-out_0s_infinite]"}
style={{
backgroundColor: color,
filter: "brightness(.3)",
left: `${boxSizeValue}px`,
height: `${boxSizeValue}px`,
width: `${boxSizeValue}px`
}}
></div>
<div
className={"absolute origin-top-left transform animate-[tlbr_2.5s_ease-out_0s_infinite]"}
style={{
backgroundColor: color,
filter: "brightness(.7)",
left: `${boxSizeValue}px`,
top: `${boxSizeValue}px`,
height: `${boxSizeValue}px`,
width: `${boxSizeValue}px`
}}
></div>
<div
className={"absolute left-0 top-0 origin-bottom-right transform animate-[tlbr_2.5s_ease-out_0s_infinite]"}
style={{ backgroundColor: color, filter: "brightness(.5)", height: `${boxSizeValue}px`, width: `${boxSizeValue}px` }}
></div>
</div>
<style jsx>{`
@keyframes tlbr {
0% {
transform: rotate(0);
}
20% {
transform: rotate(90deg);
}
40% {
transform: rotate(90deg);
}
60% {
transform: rotate(0);
}
}
@keyframes trbl {
20% {
transform: rotate(0);
}
40% {
transform: rotate(90deg);
}
60% {
transform: rotate(90deg);
}
80% {
transform: rotate(0);
}
}
@keyframes opposites {
80% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`}</style>
</div>
);
};
// Main Loaders component that renders based on variant
const Loaders: React.FC<LoaderProps & { variant?: string }> = ({ variant = "default", size = "md", className, color, speed }) => {
switch (variant) {
case "crecent":
return <CrescentLoader size={size} className={className} color={color} speed={speed} />;
case "arrows":
return <ArrowsLoader size={size} className={className} color={color} speed={speed} />;
case "dashed":
return <DashedLoader size={size} className={className} color={color} speed={speed} />;
case "double-crecent":
return <DoubleCrescentLoader size={size} className={className} color={color} speed={speed} />;
case "crecent-sharp":
return <CrescentSharpLoader size={size} className={className} color={color} speed={speed} />;
case "lines":
return <LinesLoader size={size} className={className} color={color} speed={speed} />;
case "circles":
return <CirclesLoader size={size} className={className} color={color} speed={speed} />;
case "dots":
return <DotsLoader size={size} className={className} color={color} speed={speed} />;
case "bounce":
return <BounceLoader size={size} className={className} color={color} speed={speed} />;
case "triples":
return <TriplesLoader size={size} className={className} color={color} speed={speed} />;
case "boxes":
return <BoxesLoader size={size} className={className} color={color} speed={speed} />;
default:
return <DefaultLoader size={size} className={className} color={color} speed={speed} />;
}
};
export default Loaders;