refactor: Extract reusable UI components and add TDD documentation
- Add comprehensive TDD documentation to CLAUDE.md with coverage requirements and examples - Extract reusable UI components to frontend/src/components/ui/ (Modal, FormInput, Button, Alert, etc.) - Add shared constants (schedulePresets) and utility hooks (useCrudMutation, useFormValidation) - Update frontend/CLAUDE.md with component documentation and usage examples - Refactor CreateTaskModal to use shared components and constants - Fix test assertions to be more robust and accurate across all test files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
74
frontend/src/components/ui/LoadingSpinner.tsx
Normal file
74
frontend/src/components/ui/LoadingSpinner.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
/** Size of the spinner */
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
/** Color of the spinner */
|
||||
color?: 'default' | 'white' | 'brand' | 'blue';
|
||||
/** Optional label to display below spinner */
|
||||
label?: string;
|
||||
/** Center spinner in container */
|
||||
centered?: boolean;
|
||||
/** Additional class name */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const sizeClasses = {
|
||||
xs: 'h-3 w-3',
|
||||
sm: 'h-4 w-4',
|
||||
md: 'h-6 w-6',
|
||||
lg: 'h-8 w-8',
|
||||
xl: 'h-12 w-12',
|
||||
};
|
||||
|
||||
const colorClasses = {
|
||||
default: 'text-gray-500 dark:text-gray-400',
|
||||
white: 'text-white',
|
||||
brand: 'text-brand-600 dark:text-brand-400',
|
||||
blue: 'text-blue-600 dark:text-blue-400',
|
||||
};
|
||||
|
||||
export const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({
|
||||
size = 'md',
|
||||
color = 'default',
|
||||
label,
|
||||
centered = false,
|
||||
className = '',
|
||||
}) => {
|
||||
const spinner = (
|
||||
<div className={`flex flex-col items-center gap-2 ${className}`}>
|
||||
<Loader2 className={`animate-spin ${sizeClasses[size]} ${colorClasses[color]}`} />
|
||||
{label && (
|
||||
<span className={`text-sm ${colorClasses[color]}`}>{label}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (centered) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
{spinner}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return spinner;
|
||||
};
|
||||
|
||||
/** Full page loading state */
|
||||
export const PageLoading: React.FC<{ label?: string }> = ({ label = 'Loading...' }) => (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<LoadingSpinner size="lg" color="brand" label={label} />
|
||||
</div>
|
||||
);
|
||||
|
||||
/** Inline loading indicator */
|
||||
export const InlineLoading: React.FC<{ label?: string }> = ({ label }) => (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<LoadingSpinner size="sm" />
|
||||
{label && <span className="text-sm text-gray-500 dark:text-gray-400">{label}</span>}
|
||||
</span>
|
||||
);
|
||||
|
||||
export default LoadingSpinner;
|
||||
Reference in New Issue
Block a user