- 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>
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
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;
|