Skip to content

Commit 3d24f0a

Browse files
MrHutmatiOvergaardCopilot
authored
Implementing an inline toggle button to show/hide password. (#20611)
* Implimented an inline toggle button to show/hide your password, also changed the css to accommodate these changes * Cleaned the css Added the svg's to their own const for easy reuse Added localization for the arialabel on the button Seperated the createFormLayoutItem so there is a seperate for the password input Moved all the conditional logic in the onclick event to fit inside one if/else statement * Removed old logic that added a 100ms timeout that would sometimes be enough for localization to load, and replaced it with a function. The function will try and resolve the promise by checking if the localize.terms methods returns a changed value, if not then it retries every 50ms or untill it hits a max retry of 40/2 seconds. * Re adding the hide for -ms-reveal to support Microsoft Edge browsers * Removed a console.log * Alligned the button behavior so it fits better with what we have in the uui libary. Now the button is always visible instead of appearing on hover or when in focus * Update src/Umbraco.Web.UI.Login/src/auth.element.ts Co-authored-by: Copilot <[email protected]> * Update src/Umbraco.Web.UI.Login/src/auth.element.ts Co-authored-by: Copilot <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Apply suggestion from @iOvergaard * Apply suggestion from @iOvergaard * Adding the requested changes via my own fork (#20664) Changed the logic for waitForLocallization Added the svg's as files that are imported instead of having the raw svg in the code --------- Co-authored-by: Jacob Overgaard <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 1c6d4f3 commit 3d24f0a

File tree

6 files changed

+192
-37
lines changed

6 files changed

+192
-37
lines changed
Lines changed: 6 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,84 @@
1-
#umb-login-form input {
2-
width: 100%;
3-
height: var(--input-height);
4-
box-sizing: border-box;
5-
display: block;
6-
border: 1px solid var(--uui-color-border);
7-
border-radius: var(--uui-border-radius);
8-
background-color: var(--uui-color-surface);
9-
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
1+
#umb-login-form #username-input {
2+
width: 100%;
3+
height: var(--input-height);
4+
box-sizing: border-box;
5+
display: block;
6+
border: 1px solid var(--uui-color-border);
7+
border-radius: var(--uui-border-radius);
8+
background-color: var(--uui-color-surface);
9+
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
1010
}
1111

1212
#umb-login-form uui-form-layout-item {
13-
margin-top: var(--uui-size-space-4);
14-
margin-bottom: var(--uui-size-space-4);
13+
margin-top: var(--uui-size-space-4);
14+
margin-bottom: var(--uui-size-space-4);
1515
}
1616

17-
#umb-login-form input:focus-within {
18-
border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1));
19-
outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus);
17+
#umb-login-form #username-input:focus-within {
18+
border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1));
19+
outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus);
2020
}
2121

22-
#umb-login-form input:hover:not(:focus-within) {
23-
border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2));
22+
#umb-login-form #username-input:hover:not(:focus-within) {
23+
border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2));
2424
}
2525

26-
#umb-login-form input::-ms-reveal {
27-
display: none;
26+
#umb-login-form #password-input-span button {
27+
color: var(--uui-color-default-standalone);
28+
display: inline-flex;
29+
justify-content: center;
30+
align-items: center;
31+
vertical-align: middle;
32+
min-width: 24px;
33+
min-height: 24px;
34+
border-color: transparent;
35+
background-color: transparent;
36+
padding: 0;
37+
transition-property: color;
38+
transition-duration: 0.1s;
39+
transition-timing-function: linear;
40+
}
41+
42+
#umb-login-form #password-input-span button:hover {
43+
color: var(--uui-color-default-emphasis);
44+
cursor: pointer;
45+
}
46+
47+
#umb-login-form #password-input-span {
48+
display: inline-flex;
49+
width: 100%;
50+
align-items: center;
51+
flex-wrap: nowrap;
52+
position: relative;
53+
vertical-align: middle;
54+
column-gap: 0;
55+
height: var(--input-height);
56+
box-sizing: border-box;
57+
border: 1px solid var(--uui-color-border);
58+
border-radius: var(--uui-border-radius);
59+
background-color: var(--uui-color-surface);
60+
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
2861
}
2962

30-
#umb-login-form input span {
31-
position: absolute;
32-
right: 1px;
33-
top: 50%;
34-
transform: translateY(-50%);
35-
z-index: 100;
63+
#umb-login-form #password-input-span input {
64+
flex-grow: 1;
65+
align-self: stretch;
66+
min-width: 0;
67+
display: block;
68+
border-style: none;
69+
padding: 0;
70+
outline-style: none;
3671
}
3772

38-
#umb-login-form input span svg {
39-
background-color: white;
40-
display: block;
41-
padding: .2em;
42-
width: 1.3em;
43-
height: 1.3em;
73+
#umb-login-form #password-input-span:focus-within {
74+
border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1));
75+
outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus);
76+
}
77+
78+
#umb-login-form #password-input-span:hover:not(:focus-within) {
79+
border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2));
80+
}
81+
82+
#umb-login-form input::-ms-reveal {
83+
display: none;
4484
}

src/Umbraco.Web.UI.Login/src/auth.element.ts

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import { UmbSlimBackofficeController } from './controllers';
99
// We import the authStyles here so that we can inline it in the shadow DOM that is created outside of the UmbAuthElement.
1010
import authStyles from './auth-styles.css?inline';
1111

12+
// Import the SVG files
13+
import openEyeSVG from '../public/openEye.svg?raw';
14+
import closedEyeSVG from '../public/closedEye.svg?raw';
15+
1216
// Import the main bundle
1317
import { extensions } from './umbraco-package.js';
1418

@@ -42,14 +46,73 @@ const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallb
4246
return label;
4347
};
4448

49+
const createShowPasswordToggleButton = (opts: {
50+
id: string;
51+
name: string;
52+
ariaLabelShowPassword: string;
53+
ariaLabelHidePassword: string;
54+
}) => {
55+
const button = document.createElement('button');
56+
button.id = opts.id;
57+
button.ariaLabel = opts.ariaLabelShowPassword;
58+
button.name = opts.name;
59+
button.type = 'button';
60+
61+
button.innerHTML = openEyeSVG;
62+
63+
button.onclick = () => {
64+
const passwordInput = document.getElementById('password-input') as HTMLInputElement;
65+
66+
if (passwordInput.type === 'password') {
67+
passwordInput.type = 'text';
68+
button.ariaLabel = opts.ariaLabelHidePassword;
69+
button.innerHTML = closedEyeSVG;
70+
} else {
71+
passwordInput.type = 'password';
72+
button.ariaLabel = opts.ariaLabelShowPassword;
73+
button.innerHTML = openEyeSVG;
74+
}
75+
76+
passwordInput.focus();
77+
};
78+
79+
return button;
80+
};
81+
82+
const createShowPasswordToggleItem = (button: HTMLButtonElement) => {
83+
const span = document.createElement('span');
84+
span.id = 'password-show-toggle-span';
85+
span.appendChild(button);
86+
87+
return span;
88+
};
89+
4590
const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement) => {
4691
const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement;
92+
4793
formLayoutItem.appendChild(label);
4894
formLayoutItem.appendChild(input);
4995

5096
return formLayoutItem;
5197
};
5298

99+
const createFormLayoutPasswordItem = (
100+
label: HTMLLabelElement,
101+
input: HTMLInputElement,
102+
showPasswordToggle: HTMLSpanElement
103+
) => {
104+
const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement;
105+
106+
formLayoutItem.appendChild(label);
107+
const span = document.createElement('span');
108+
span.id = 'password-input-span';
109+
span.appendChild(input);
110+
span.appendChild(showPasswordToggle);
111+
formLayoutItem.appendChild(span);
112+
113+
return formLayoutItem;
114+
};
115+
53116
const createForm = (elements: HTMLElement[]) => {
54117
const styles = document.createElement('style');
55118
styles.innerHTML = authStyles;
@@ -112,6 +175,8 @@ export default class UmbAuthElement extends UmbLitElement {
112175
_passwordInput?: HTMLInputElement;
113176
_usernameLabel?: HTMLLabelElement;
114177
_passwordLabel?: HTMLLabelElement;
178+
_passwordShowPasswordToggleItem?: HTMLSpanElement;
179+
_passwordShowPasswordToggleButton?: HTMLButtonElement;
115180

116181
#authContext = new UmbAuthContext(this, UMB_AUTH_CONTEXT);
117182

@@ -133,11 +198,35 @@ export default class UmbAuthElement extends UmbLitElement {
133198
// Register the main package for Umbraco.Auth
134199
umbExtensionsRegistry.registerMany(extensions);
135200

136-
setTimeout(() => {
137-
requestAnimationFrame(() => {
138-
this.#initializeForm();
139-
});
140-
}, 100);
201+
// Wait for localization to be ready before loading the form
202+
await this.#waitForLocalization();
203+
204+
this.#initializeForm();
205+
}
206+
207+
async #waitForLocalization(): Promise<void> {
208+
return new Promise((resolve, reject) => {
209+
let retryCount = 0;
210+
// Retries 40 times with a 50ms interval = 2 seconds
211+
const maxRetries = 40;
212+
213+
// We check periodically until it is available or we reach the max retries
214+
const checkInterval = setInterval(() => {
215+
// If we reach max retries, we give up and reject the promise
216+
if (retryCount > maxRetries) {
217+
clearInterval(checkInterval);
218+
reject('Localization not available');
219+
return;
220+
}
221+
// Check if localization is available
222+
if (this.localize.term('auth_showPassword') !== 'auth_showPassword') {
223+
clearInterval(checkInterval);
224+
resolve();
225+
return;
226+
}
227+
retryCount++;
228+
}, 50);
229+
});
141230
}
142231

143232
disconnectedCallback() {
@@ -148,6 +237,8 @@ export default class UmbAuthElement extends UmbLitElement {
148237
this._usernameInput?.remove();
149238
this._passwordLabel?.remove();
150239
this._passwordInput?.remove();
240+
this._passwordShowPasswordToggleItem?.remove();
241+
this._passwordShowPasswordToggleButton?.remove();
151242
}
152243

153244
/**
@@ -173,6 +264,13 @@ export default class UmbAuthElement extends UmbLitElement {
173264
autocomplete: 'current-password',
174265
inputmode: '',
175266
});
267+
this._passwordShowPasswordToggleButton = createShowPasswordToggleButton({
268+
id: 'password-show-toggle',
269+
name: 'password-show-toggle',
270+
ariaLabelShowPassword: this.localize.term('auth_showPassword'),
271+
ariaLabelHidePassword: this.localize.term('auth_hidePassword'),
272+
});
273+
this._passwordShowPasswordToggleItem = createShowPasswordToggleItem(this._passwordShowPasswordToggleButton);
176274
this._usernameLabel = createLabel({
177275
forId: 'username-input',
178276
localizeAlias: this.usernameIsEmail ? 'auth_email' : 'auth_username',
@@ -183,9 +281,12 @@ export default class UmbAuthElement extends UmbLitElement {
183281
localizeAlias: 'auth_password',
184282
localizeFallback: 'Password',
185283
});
186-
187284
this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput);
188-
this._passwordLayoutItem = createFormLayoutItem(this._passwordLabel, this._passwordInput);
285+
this._passwordLayoutItem = createFormLayoutPasswordItem(
286+
this._passwordLabel,
287+
this._passwordInput,
288+
this._passwordShowPasswordToggleItem
289+
);
189290

190291
this._form = createForm([this._usernameLayoutItem, this._passwordLayoutItem]);
191292

src/Umbraco.Web.UI.Login/src/localization/lang/da.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@ export default {
4747
returnToLogin: 'Tilbage til log ind',
4848
localLoginDisabled: 'Desværre er det ikke muligt at logge ind direkte. Det er blevet deaktiveret af en login-udbyder.',
4949
friendlyGreeting: 'Hej!',
50+
showPassword: 'Vis adgangskode',
51+
hidePassword: 'Skjul adgangskode',
5052
},
5153
} satisfies UmbLocalizationDictionary;

src/Umbraco.Web.UI.Login/src/localization/lang/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@ export default {
4747
returnToLogin: 'Return to login',
4848
localLoginDisabled: 'Unfortunately, direct login is not possible. It has been disabled by a provider.',
4949
friendlyGreeting: 'Hello',
50+
showPassword: 'Show password',
51+
hidePassword: 'Hide password',
5052
},
5153
} satisfies UmbLocalizationDictionary;

0 commit comments

Comments
 (0)