Spaces:
Sleeping
Sleeping
File size: 1,656 Bytes
b64de39 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /**
* LoadingSpinner - Configurable loading indicator.
*
* Sizes: sm | md | lg | xl
* Can display an optional message below the spinner.
* Supports full-screen overlay mode.
*/
import PropTypes from "prop-types";
const SIZE_MAP = {
sm: "h-5 w-5",
md: "h-8 w-8",
lg: "h-12 w-12",
xl: "h-16 w-16",
};
function LoadingSpinner({
size = "md",
message,
fullScreen = false,
className = "",
}) {
const spinner = (
<div
className={`flex flex-col items-center justify-center gap-3 ${className}`}
role="status"
aria-label={message || "Loading"}
>
<svg
className={`animate-spin text-primary-600 ${SIZE_MAP[size] || SIZE_MAP.md}`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
aria-hidden="true"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
{message && (
<p className="text-sm text-neutral-600">{message}</p>
)}
</div>
);
if (fullScreen) {
return (
<div className="fixed inset-0 z-40 flex items-center justify-center bg-white/80 backdrop-blur-sm">
{spinner}
</div>
);
}
return spinner;
}
LoadingSpinner.propTypes = {
size: PropTypes.oneOf(["sm", "md", "lg", "xl"]),
message: PropTypes.string,
fullScreen: PropTypes.bool,
className: PropTypes.string,
};
export default LoadingSpinner;
|