password-strength-indicator.svelte RawHistoryFolder
1<script lang="ts">
2 import {
3 validatePasswordStrength,
4 type PasswordConstraints,
5 DEFAULT_PASSWORD_CONSTRAINTS
6 } from '../validation/password';
7
8 let {
9 password = '',
10 constraints = DEFAULT_PASSWORD_CONSTRAINTS,
11 showDetails = true
12 } = $props<{
13 password: string;
14 constraints?: PasswordConstraints;
15 showDetails?: boolean;
16 }>();
17
18 // Reactive validation result
19 let validation = $derived(validatePasswordStrength(password, constraints));
20
21 // Get strength color and label
22 function getStrengthInfo(strength: number) {
23 if (strength < 30) return { color: '#ff4444', label: 'Weak', class: 'weak' };
24 if (strength < 60) return { color: '#ffaa00', label: 'Fair', class: 'fair' };
25 if (strength < 80) return { color: '#44aa44', label: 'Good', class: 'good' };
26 return { color: '#00aa00', label: 'Strong', class: 'strong' };
27 }
28
29 let strengthInfo = $derived(getStrengthInfo(validation.strength));
30</script>
31
32<div class="password-strength-indicator">
33 {#if password.length > 0}
34 <!-- Strength bar -->
35 <div class="strength-bar">
36 <div class="strength-bar-bg">
37 <div
38 class="strength-bar-fill {strengthInfo.class}"
39 style="width: {validation.strength}%; background-color: {strengthInfo.color};"
40 ></div>
41 </div>
42 <span class="strength-label {strengthInfo.class}">{strengthInfo.label}</span>
43 </div>
44
45 {#if showDetails}
46 <!-- Requirements checklist -->
47 <div class="requirements-list">
48 {#each validation.requirements as requirement (requirement.message)}
49 <div class="requirement {requirement.met ? 'met' : 'unmet'}">
50 <span class="requirement-icon">
51 {requirement.met ? '✓' : '✗'}
52 </span>
53 <span class="requirement-text">{requirement.message}</span>
54 </div>
55 {/each}
56 </div>
57
58 <!-- Error messages -->
59 {#if validation.errors.length > 0}
60 <div class="error-messages">
61 {#each validation.errors as error (error)}
62 <div class="error-message">{error}</div>
63 {/each}
64 </div>
65 {/if}
66 {/if}
67 {/if}
68</div>
69
70<style>
71 .password-strength-indicator {
72 margin-top: 8px;
73 font-size: 0.875rem;
74 }
75
76 .strength-bar {
77 display: flex;
78 align-items: center;
79 gap: 8px;
80 margin-bottom: 8px;
81 }
82
83 .strength-bar-bg {
84 flex: 1;
85 height: 6px;
86 background-color: #ddd;
87 border-radius: 3px;
88 overflow: hidden;
89 }
90
91 .strength-bar-fill {
92 height: 100%;
93 transition:
94 width 0.3s ease,
95 background-color 0.3s ease;
96 border-radius: 3px;
97 }
98
99 .strength-label {
100 min-width: 50px;
101 font-weight: bold;
102 font-size: 0.75rem;
103 }
104
105 .strength-label.weak {
106 color: #ff4444;
107 }
108 .strength-label.fair {
109 color: #ffaa00;
110 }
111 .strength-label.good {
112 color: #44aa44;
113 }
114 .strength-label.strong {
115 color: #00aa00;
116 }
117
118 .requirements-list {
119 display: flex;
120 flex-direction: column;
121 gap: 4px;
122 margin-bottom: 8px;
123 }
124
125 .requirement {
126 display: flex;
127 align-items: center;
128 gap: 6px;
129 font-size: 0.75rem;
130 }
131
132 .requirement-icon {
133 width: 12px;
134 text-align: center;
135 font-weight: bold;
136 }
137
138 .requirement.met {
139 color: #00aa00;
140 }
141
142 .requirement.met .requirement-icon {
143 color: #00aa00;
144 }
145
146 .requirement.unmet {
147 color: #888;
148 }
149
150 .requirement.unmet .requirement-icon {
151 color: #ff4444;
152 }
153
154 .error-messages {
155 display: flex;
156 flex-direction: column;
157 gap: 4px;
158 }
159
160 .error-message {
161 color: #ff4444;
162 font-size: 0.75rem;
163 }
164
165 /* Compact mode styles */
166 :global(.compact) .password-strength-indicator {
167 font-size: 0.75rem;
168 }
169
170 :global(.compact) .requirements-list {
171 display: none;
172 }
173
174 :global(.compact) .strength-bar {
175 margin-bottom: 4px;
176 }
177</style>