Files
klz-cables.com/components/ui/Loading.tsx
2025-12-29 18:18:48 +01:00

224 lines
5.5 KiB
TypeScript

import React, { forwardRef, HTMLAttributes } from 'react';
import { cn } from '../../lib/utils';
// Loading sizes
type LoadingSize = 'sm' | 'md' | 'lg' | 'xl';
// Loading variants
type LoadingVariant = 'primary' | 'secondary' | 'neutral' | 'contrast';
// Loading props interface
interface LoadingProps extends HTMLAttributes<HTMLDivElement> {
size?: LoadingSize;
variant?: LoadingVariant;
overlay?: boolean;
text?: string;
fullscreen?: boolean;
}
// Helper function to get size styles
const getSizeStyles = (size: LoadingSize) => {
switch (size) {
case 'sm':
return 'w-4 h-4 border-2';
case 'md':
return 'w-8 h-8 border-4';
case 'lg':
return 'w-12 h-12 border-4';
case 'xl':
return 'w-16 h-16 border-4';
default:
return 'w-8 h-8 border-4';
}
};
// Helper function to get variant styles
const getVariantStyles = (variant: LoadingVariant) => {
switch (variant) {
case 'primary':
return 'border-primary';
case 'secondary':
return 'border-secondary';
case 'neutral':
return 'border-gray-300';
case 'contrast':
return 'border-white';
default:
return 'border-primary';
}
};
// Helper function to get text size
const getTextSize = (size: LoadingSize) => {
switch (size) {
case 'sm':
return 'text-sm';
case 'md':
return 'text-base';
case 'lg':
return 'text-lg';
case 'xl':
return 'text-xl';
default:
return 'text-base';
}
};
// Main Loading Component
export const Loading = forwardRef<HTMLDivElement, LoadingProps>(
({
size = 'md',
variant = 'primary',
overlay = false,
text,
fullscreen = false,
className = '',
...props
}, ref) => {
const spinner = (
<div
className={cn(
'animate-spin rounded-full',
'border-t-transparent',
getSizeStyles(size),
getVariantStyles(variant),
className
)}
{...props}
/>
);
if (overlay) {
return (
<div
ref={ref}
className={cn(
'fixed inset-0 z-50 flex items-center justify-center',
'bg-black/50 backdrop-blur-sm',
fullscreen && 'w-screen h-screen'
)}
>
<div className="flex flex-col items-center gap-3">
{spinner}
{text && (
<span className={cn('text-white font-medium', getTextSize(size))}>
{text}
</span>
)}
</div>
</div>
);
}
return (
<div
ref={ref}
className={cn(
'flex flex-col items-center justify-center gap-3',
fullscreen && 'w-screen h-screen'
)}
>
{spinner}
{text && (
<span className={cn('text-gray-700 font-medium', getTextSize(size))}>
{text}
</span>
)}
</div>
);
}
);
Loading.displayName = 'Loading';
// Loading Button Component
interface LoadingButtonProps extends HTMLAttributes<HTMLDivElement> {
size?: LoadingSize;
variant?: LoadingVariant;
text?: string;
}
export const LoadingButton = forwardRef<HTMLDivElement, LoadingButtonProps>(
({ size = 'md', variant = 'primary', text = 'Loading...', className = '', ...props }, ref) => {
return (
<div
ref={ref}
className={cn(
'inline-flex items-center gap-2 px-4 py-2 rounded-lg',
'bg-gray-100 text-gray-700',
className
)}
{...props}
>
<div
className={cn(
'animate-spin rounded-full border-t-transparent',
getSizeStyles(size === 'sm' ? 'sm' : 'md'),
getVariantStyles(variant)
)}
/>
<span className="font-medium">{text}</span>
</div>
);
}
);
LoadingButton.displayName = 'LoadingButton';
// Loading Skeleton Component
interface LoadingSkeletonProps extends HTMLAttributes<HTMLDivElement> {
width?: string | number;
height?: string | number;
rounded?: boolean;
className?: string;
}
export const LoadingSkeleton = forwardRef<HTMLDivElement, LoadingSkeletonProps>(
({ width = '100%', height = '1rem', rounded = false, className = '', ...props }, ref) => {
// Convert numeric values to Tailwind width classes
const getWidthClass = (width: string | number) => {
if (typeof width === 'number') {
if (width <= 32) return 'w-8';
if (width <= 64) return 'w-16';
if (width <= 128) return 'w-32';
if (width <= 192) return 'w-48';
if (width <= 256) return 'w-64';
return 'w-full';
}
return width === '100%' ? 'w-full' : width;
};
// Convert numeric values to Tailwind height classes
const getHeightClass = (height: string | number) => {
if (typeof height === 'number') {
if (height <= 8) return 'h-2';
if (height <= 16) return 'h-4';
if (height <= 24) return 'h-6';
if (height <= 32) return 'h-8';
if (height <= 48) return 'h-12';
if (height <= 64) return 'h-16';
return 'h-auto';
}
return height === '1rem' ? 'h-4' : height;
};
return (
<div
ref={ref}
className={cn(
'animate-pulse bg-gray-200',
rounded && 'rounded-md',
getWidthClass(width),
getHeightClass(height),
className
)}
{...props}
/>
);
}
);
LoadingSkeleton.displayName = 'LoadingSkeleton';
// Export types for external use
export type { LoadingProps, LoadingSize, LoadingVariant, LoadingButtonProps, LoadingSkeletonProps };