Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 1x 444x 444x 444x 444x 444x 444x 444x 444x 444x 444x 444x 444x 444x 2x 442x 276x 166x 444x 444x 444x 444x 2x 2x 444x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 442x 442x 442x 442x 443x 444x 444x 444x 444x 444x 444x 276x 166x 444x 444x 444x 1x | import React from 'react'; export interface WidgetContainerProps { title: string; children: React.ReactNode; isLoading?: boolean; loading?: boolean; // For backward compatibility error?: string | null | Error; // Accept Error objects too className?: string; testId?: string; errorContent?: React.ReactNode; icon?: string | React.ReactNode; actions?: React.ReactNode; } /** * Container component for dashboard widgets * * ## Business Perspective * * This component provides a consistent presentation for all security dashboard * widgets, with standardized loading, error states, and styling. Consistency * in presentation helps users navigate security information more effectively. 🎨 */ export const WidgetContainer: React.FC<WidgetContainerProps> = ({ title, children, isLoading = false, loading = false, // Accept loading prop for backward compatibility error = null, className = '', testId, errorContent, icon, actions }) => { // For backward compatibility - support older code using "loading" prop const isLoadingState = isLoading || loading; // Create unique test IDs for different widget states const containerTestId = error ? `widget-container-error${testId ? `-${testId}` : ''}` : isLoadingState ? `widget-container-loading-container${testId ? `-${testId}` : ''}` : `widget-container${testId ? `-${testId}` : ''}`; const spinnerTestId = `widget-spinner${testId ? `-${testId}` : ''}`; const errorTestId = `test-widget-error${testId ? `-${testId}` : ''}`; // Convert Error objects to strings let errorMessage: string | null = null; if (error !== null) { errorMessage = error instanceof Error ? error.message : String(error); } // Handle error state if (errorMessage) { return ( <div className={`widget-container widget-error border border-red-300 rounded-lg shadow-sm ${className}`} data-testid={containerTestId}> <div className="widget-header bg-red-50 dark:bg-red-900 dark:bg-opacity-20 px-4 py-3 border-b border-red-200 dark:border-red-800 rounded-t-lg"> <h3 className="text-lg font-medium text-red-800 dark:text-red-300 flex items-center"> <span className="mr-2">⚠️</span> {title} </h3> </div> <div className="widget-body p-4 bg-white dark:bg-gray-900"> <div className="text-red-600 dark:text-red-400" data-testid={errorTestId}> {errorMessage} </div> {errorContent && <div>{errorContent}</div>} </div> </div> ); } // Handle loading or normal state return ( <div className={`widget-container border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm ${className}`} data-testid={containerTestId}> <div className="widget-header bg-gray-50 dark:bg-gray-800 px-4 py-3 border-b border-gray-200 dark:border-gray-700 rounded-t-lg flex justify-between items-center"> <h3 className="text-lg font-medium text-gray-800 dark:text-gray-200 flex items-center"> {icon && <span className="mr-2">{icon}</span>} {title} </h3> {actions && <div className="widget-actions">{actions}</div>} </div> <div className={`widget-body p-4 bg-white dark:bg-gray-900 rounded-b-lg ${isLoadingState ? 'flex items-center justify-center min-h-[100px]' : ''}`}> {isLoadingState ? ( <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" data-testid={spinnerTestId} /> ) : ( children )} </div> </div> ); }; export default WidgetContainer; |