- Create src/types/ with organized type files: - api.types.ts, auth.types.ts, construction.types.ts - estimates.types.ts, hse.types.ts, common.types.ts - Create src/utils/ with utilities: - formatters.ts (formatCurrency, formatDate, etc.) - validators.ts (isValidEmail, isValidRFC, etc.) - constants.ts (status options, color maps) - Create src/components/common/ with reusable components: - DataTable, Modal, ConfirmDialog - FormField (TextInput, SelectField, TextareaField) - StatusBadge, SearchInput, PageHeader - ActionButtons, LoadingSpinner, EmptyState - Refactor pages to use common components: - FraccionamientosPage: 385 -> 285 lines - PresupuestosPage: 412 -> 297 lines - IncidentesPage: 741 -> 253 lines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
46 lines
964 B
TypeScript
46 lines
964 B
TypeScript
/**
|
|
* LoadingSpinner - Reusable loading indicator component
|
|
*/
|
|
|
|
import clsx from 'clsx';
|
|
|
|
interface LoadingSpinnerProps {
|
|
size?: 'sm' | 'md' | 'lg';
|
|
className?: string;
|
|
}
|
|
|
|
const sizeClasses = {
|
|
sm: 'w-4 h-4',
|
|
md: 'w-8 h-8',
|
|
lg: 'w-12 h-12',
|
|
};
|
|
|
|
export function LoadingSpinner({ size = 'md', className }: LoadingSpinnerProps) {
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'animate-spin rounded-full border-2 border-gray-300 border-t-blue-600',
|
|
sizeClasses[size],
|
|
className
|
|
)}
|
|
role="status"
|
|
aria-label="Cargando"
|
|
>
|
|
<span className="sr-only">Cargando...</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface LoadingOverlayProps {
|
|
message?: string;
|
|
}
|
|
|
|
export function LoadingOverlay({ message = 'Cargando...' }: LoadingOverlayProps) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center p-8">
|
|
<LoadingSpinner size="lg" />
|
|
<p className="mt-4 text-gray-500">{message}</p>
|
|
</div>
|
|
);
|
|
}
|