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>

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close