import React from "react"; import { View } from "react-native"; import { MutedText } from "../../components/text"; interface PasswordStrengthMeterProps { password: string; /** * Render the (empty) meter instead of nothing while `password` is blank, so * the row occupies the same height typed or not. The consolidated * AuthScreen needs this: its card height is locked to the tallest mode, and * a meter that appears on first keystroke would grow the card right back * out. Off by default — the standalone Register/ResetPassword screens want * the row gone until there is something to measure. */ reserveSpace?: boolean; } export function calculatePasswordStrength(pass: string) { let strength: number; const hasLower = /[a-z]/.test(pass); const hasUpper = /[A-Z]/.test(pass); const hasNumber = /[0-9]/.test(pass); const hasSpecial = /[^A-Za-z0-9]/.test(pass); const complexityCount = [hasLower, hasUpper, hasNumber, hasSpecial].filter(Boolean).length; if (complexityCount >= 4) { strength = 60; } else if (complexityCount === 3) { strength = 40; } else if (complexityCount === 2) { strength = 20; } else { strength = 10; } if (pass.length >= 16) { strength += 40; } else if (pass.length >= 12) { strength += 20; } else if (pass.length >= 8) { strength += 10; } if (pass.length < 8) { strength = Math.min(strength, 25); } else if (pass.length < 12) { strength = Math.min(strength, 50); } else if (pass.length < 16) { strength = Math.min(strength, 75); } let color: string; let label: string; if (strength <= 25) { color = "#EF4444"; label = "Weak"; } else if (strength <= 50) { color = "#F59E0B"; label = "Moderate"; } else if (strength <= 75) { color = "#22C55E"; label = "Strong"; } else { color = "#10B981"; label = "Very Strong"; } return { strength: Math.min(strength, 100), color, label }; } const PasswordStrengthMeter = React.memo(function PasswordStrengthMeter({ password, reserveSpace = false, }: PasswordStrengthMeterProps) { if (!password && !reserveSpace) { return null; } // Blank password in reserve mode: render the same tree fully transparent, so // the block holds its exact height without showing an empty gauge that reads // as broken. `label`/`strength` are placeholders that are never visible. const blank = !password; const { strength, color, label } = blank ? { strength: 0, color: "#333333", label: "Weak" } : calculatePasswordStrength(password); return ( {[0, 1, 2, 3, 4].map((i) => { const threshold = (i + 1) * 20; const filled = strength >= threshold; return ( ); })} {label} {strength}% ); }); export default PasswordStrengthMeter;