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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 1x 1x 45x 45x 45x 45x 45x 45x 45x 45x 45x 41x 45x 45x 45x 45x 7x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 1x | import React, { useMemo } from "react"; interface SecurityRiskScoreProps { /** * The security score to display (0-100) */ score: number; /** * Maximum possible score (defaults to 100) */ maxScore?: number; /** * Label to display under the score */ label: string; /** * Optional CSS class name */ className?: string; /** * Optional test ID for automated testing */ testId?: string; } /** * Displays a circular gauge chart showing a security risk score * * ## Business Perspective * * This component provides a quantitative measure of security posture in an * easy-to-understand format. The numerical score and color-coding help * business stakeholders quickly gauge security maturity. 📈 */ export function SecurityRiskScore({ score, maxScore = 100, label, className = "", testId, }: SecurityRiskScoreProps): React.ReactElement { // Normalize score as a percentage const normalizedScore = useMemo(() => { if (score <= 0) return 0; if (score >= maxScore) return 100; return (score / maxScore) * 100; }, [score, maxScore]); // Determine color based on score range const scoreColor = useMemo(() => { if (normalizedScore >= 75) return "text-green-500 dark:text-green-400"; if (normalizedScore >= 50) return "text-yellow-500 dark:text-yellow-400"; return "text-red-500 dark:text-red-400"; }, [normalizedScore]); // Calculate the stroke dash offset for the circular progress const circumference = 2 * Math.PI * 28; // 2πr where r=28 const strokeDashArray = `${circumference}`; const strokeDashOffset = ((100 - normalizedScore) / 100) * circumference; return ( <div className={`flex flex-col items-center ${className}`} data-testid={testId} > <div className="relative"> <svg width="64" height="64" viewBox="0 0 64 64"> {/* Background circle */} <circle cx="32" cy="32" r="28" fill="none" stroke="#e5e7eb" strokeWidth="8" className="dark:stroke-gray-700" /> {/* Score indicator */} <circle cx="32" cy="32" r="28" fill="none" strokeLinecap="round" stroke="currentColor" strokeWidth="8" strokeDasharray={strokeDashArray} strokeDashoffset={strokeDashOffset} transform="rotate(-90 32 32)" className={scoreColor} /> </svg> <div className="absolute inset-0 flex items-center justify-center"> <span className={`text-lg font-bold ${scoreColor}`} data-testid={`${testId}-value`} > {Math.round(normalizedScore)} </span> </div> </div> <span className="text-xs text-gray-600 dark:text-gray-400 mt-1" data-testid={`${testId}-label`} > {label} </span> </div> ); } export default SecurityRiskScore; |