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:
106
frontend/src/components/ui/Alert.tsx
Normal file
106
frontend/src/components/ui/Alert.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { AlertCircle, CheckCircle, Info, AlertTriangle, X } from 'lucide-react';
|
||||
|
||||
type AlertVariant = 'error' | 'success' | 'warning' | 'info';
|
||||
|
||||
interface AlertProps {
|
||||
variant: AlertVariant;
|
||||
message: string | React.ReactNode;
|
||||
title?: string;
|
||||
onDismiss?: () => void;
|
||||
className?: string;
|
||||
/** Compact mode for inline alerts */
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const variantConfig: Record<AlertVariant, {
|
||||
icon: React.ReactNode;
|
||||
containerClass: string;
|
||||
textClass: string;
|
||||
titleClass: string;
|
||||
}> = {
|
||||
error: {
|
||||
icon: <AlertCircle size={20} />,
|
||||
containerClass: 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800',
|
||||
textClass: 'text-red-800 dark:text-red-200',
|
||||
titleClass: 'text-red-900 dark:text-red-100',
|
||||
},
|
||||
success: {
|
||||
icon: <CheckCircle size={20} />,
|
||||
containerClass: 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800',
|
||||
textClass: 'text-green-800 dark:text-green-200',
|
||||
titleClass: 'text-green-900 dark:text-green-100',
|
||||
},
|
||||
warning: {
|
||||
icon: <AlertTriangle size={20} />,
|
||||
containerClass: 'bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800',
|
||||
textClass: 'text-amber-800 dark:text-amber-200',
|
||||
titleClass: 'text-amber-900 dark:text-amber-100',
|
||||
},
|
||||
info: {
|
||||
icon: <Info size={20} />,
|
||||
containerClass: 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800',
|
||||
textClass: 'text-blue-800 dark:text-blue-200',
|
||||
titleClass: 'text-blue-900 dark:text-blue-100',
|
||||
},
|
||||
};
|
||||
|
||||
export const Alert: React.FC<AlertProps> = ({
|
||||
variant,
|
||||
message,
|
||||
title,
|
||||
onDismiss,
|
||||
className = '',
|
||||
compact = false,
|
||||
}) => {
|
||||
const config = variantConfig[variant];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${compact ? 'p-2' : 'p-3'} border rounded-lg ${config.containerClass} ${className}`}
|
||||
role="alert"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className={`flex-shrink-0 ${config.textClass}`}>{config.icon}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
{title && (
|
||||
<p className={`font-medium ${config.titleClass} ${compact ? 'text-sm' : ''}`}>
|
||||
{title}
|
||||
</p>
|
||||
)}
|
||||
<div className={`${compact ? 'text-xs' : 'text-sm'} ${config.textClass} ${title ? 'mt-1' : ''}`}>
|
||||
{typeof message === 'string' ? <p>{message}</p> : message}
|
||||
</div>
|
||||
</div>
|
||||
{onDismiss && (
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className={`flex-shrink-0 p-1 rounded hover:bg-black/5 dark:hover:bg-white/5 transition-colors ${config.textClass}`}
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** Convenience components */
|
||||
export const ErrorMessage: React.FC<Omit<AlertProps, 'variant'>> = (props) => (
|
||||
<Alert variant="error" {...props} />
|
||||
);
|
||||
|
||||
export const SuccessMessage: React.FC<Omit<AlertProps, 'variant'>> = (props) => (
|
||||
<Alert variant="success" {...props} />
|
||||
);
|
||||
|
||||
export const WarningMessage: React.FC<Omit<AlertProps, 'variant'>> = (props) => (
|
||||
<Alert variant="warning" {...props} />
|
||||
);
|
||||
|
||||
export const InfoMessage: React.FC<Omit<AlertProps, 'variant'>> = (props) => (
|
||||
<Alert variant="info" {...props} />
|
||||
);
|
||||
|
||||
export default Alert;
|
||||
Reference in New Issue
Block a user