Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Umbraco.Web.UI.Login/public/closedEye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/Umbraco.Web.UI.Login/public/openEye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 70 additions & 30 deletions src/Umbraco.Web.UI.Login/src/auth-styles.css
Original file line number Diff line number Diff line change
@@ -1,44 +1,84 @@
#umb-login-form input {
width: 100%;
height: var(--input-height);
box-sizing: border-box;
display: block;
border: 1px solid var(--uui-color-border);
border-radius: var(--uui-border-radius);
background-color: var(--uui-color-surface);
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
#umb-login-form #username-input {
width: 100%;
height: var(--input-height);
box-sizing: border-box;
display: block;
border: 1px solid var(--uui-color-border);
border-radius: var(--uui-border-radius);
background-color: var(--uui-color-surface);
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
}

#umb-login-form uui-form-layout-item {
margin-top: var(--uui-size-space-4);
margin-bottom: var(--uui-size-space-4);
margin-top: var(--uui-size-space-4);
margin-bottom: var(--uui-size-space-4);
}

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

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

#umb-login-form input::-ms-reveal {
display: none;
#umb-login-form #password-input-span button {
color: var(--uui-color-default-standalone);
display: inline-flex;
justify-content: center;
align-items: center;
vertical-align: middle;
min-width: 24px;
min-height: 24px;
border-color: transparent;
background-color: transparent;
padding: 0;
transition-property: color;
transition-duration: 0.1s;
transition-timing-function: linear;
}

#umb-login-form #password-input-span button:hover {
color: var(--uui-color-default-emphasis);
cursor: pointer;
}

#umb-login-form #password-input-span {
display: inline-flex;
width: 100%;
align-items: center;
flex-wrap: nowrap;
position: relative;
vertical-align: middle;
column-gap: 0;
height: var(--input-height);
box-sizing: border-box;
border: 1px solid var(--uui-color-border);
border-radius: var(--uui-border-radius);
background-color: var(--uui-color-surface);
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
}

#umb-login-form input span {
position: absolute;
right: 1px;
top: 50%;
transform: translateY(-50%);
z-index: 100;
#umb-login-form #password-input-span input {
flex-grow: 1;
align-self: stretch;
min-width: 0;
display: block;
border-style: none;
padding: 0;
outline-style: none;
}

#umb-login-form input span svg {
background-color: white;
display: block;
padding: .2em;
width: 1.3em;
height: 1.3em;
#umb-login-form #password-input-span:focus-within {
border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1));
outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus);
}

#umb-login-form #password-input-span:hover:not(:focus-within) {
border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2));
}

#umb-login-form input::-ms-reveal {
display: none;
}
115 changes: 108 additions & 7 deletions src/Umbraco.Web.UI.Login/src/auth.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
// We import the authStyles here so that we can inline it in the shadow DOM that is created outside of the UmbAuthElement.
import authStyles from './auth-styles.css?inline';

// Import the SVG files
import openEyeSVG from '../public/openEye.svg?raw';
import closedEyeSVG from '../public/closedEye.svg?raw';

// Import the main bundle
import { extensions } from './umbraco-package.js';

Expand Down Expand Up @@ -42,14 +46,73 @@
return label;
};

const createShowPasswordToggleButton = (opts: {
id: string;
name: string;
ariaLabelShowPassword: string;
ariaLabelHidePassword: string;
}) => {
const button = document.createElement('button');
button.id = opts.id;
button.ariaLabel = opts.ariaLabelShowPassword;
button.name = opts.name;
button.type = 'button';

button.innerHTML = openEyeSVG;

button.onclick = () => {
const passwordInput = document.getElementById('password-input') as HTMLInputElement;

if (passwordInput.type === 'password') {
passwordInput.type = 'text';
button.ariaLabel = opts.ariaLabelHidePassword;
button.innerHTML = closedEyeSVG;
} else {
passwordInput.type = 'password';
button.ariaLabel = opts.ariaLabelShowPassword;
button.innerHTML = openEyeSVG;
}

passwordInput.focus();
};

return button;
};

const createShowPasswordToggleItem = (button: HTMLButtonElement) => {
const span = document.createElement('span');
span.id = 'password-show-toggle-span';
span.appendChild(button);

return span;
};

const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement) => {
const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement;

formLayoutItem.appendChild(label);
formLayoutItem.appendChild(input);

return formLayoutItem;
};

const createFormLayoutPasswordItem = (
label: HTMLLabelElement,
input: HTMLInputElement,
showPasswordToggle: HTMLSpanElement
) => {
const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement;

formLayoutItem.appendChild(label);
const span = document.createElement('span');
span.id = 'password-input-span';
span.appendChild(input);
span.appendChild(showPasswordToggle);
formLayoutItem.appendChild(span);

return formLayoutItem;
};

const createForm = (elements: HTMLElement[]) => {
const styles = document.createElement('style');
styles.innerHTML = authStyles;
Expand Down Expand Up @@ -112,6 +175,8 @@
_passwordInput?: HTMLInputElement;
_usernameLabel?: HTMLLabelElement;
_passwordLabel?: HTMLLabelElement;
_passwordShowPasswordToggleItem?: HTMLSpanElement;
_passwordShowPasswordToggleButton?: HTMLButtonElement;

#authContext = new UmbAuthContext(this, UMB_AUTH_CONTEXT);

Expand All @@ -133,11 +198,35 @@
// Register the main package for Umbraco.Auth
umbExtensionsRegistry.registerMany(extensions);

setTimeout(() => {
requestAnimationFrame(() => {
this.#initializeForm();
});
}, 100);
// Wait for localization to be ready before loading the form
await this.#waitForLocalization();

this.#initializeForm();
}

async #waitForLocalization(): Promise<void> {
return new Promise((resolve, reject) => {
let retryCount = 0;
// Retries 40 times with a 50ms interval = 2 seconds
const maxRetries = 40;

// We check periodically until it is available or we reach the max retries
const checkInterval = setInterval(() => {
// If we reach max retries, we give up and reject the promise
if (retryCount > maxRetries) {
clearInterval(checkInterval);
reject('Localization not available');
return;
}
// Check if localization is available
if (this.localize.term('auth_showPassword') !== 'auth_showPassword') {
clearInterval(checkInterval);
resolve();
return;
}
retryCount++;
}, 50);
});
}

disconnectedCallback() {
Expand All @@ -148,6 +237,8 @@
this._usernameInput?.remove();
this._passwordLabel?.remove();
this._passwordInput?.remove();
this._passwordShowPasswordToggleItem?.remove();
this._passwordShowPasswordToggleButton?.remove();

Check warning on line 241 in src/Umbraco.Web.UI.Login/src/auth.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Complex Method

UmbAuthElement.disconnectedCallback has a cyclomatic complexity of 9, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}

/**
Expand All @@ -173,6 +264,13 @@
autocomplete: 'current-password',
inputmode: '',
});
this._passwordShowPasswordToggleButton = createShowPasswordToggleButton({
id: 'password-show-toggle',
name: 'password-show-toggle',
ariaLabelShowPassword: this.localize.term('auth_showPassword'),
ariaLabelHidePassword: this.localize.term('auth_hidePassword'),
});
this._passwordShowPasswordToggleItem = createShowPasswordToggleItem(this._passwordShowPasswordToggleButton);
this._usernameLabel = createLabel({
forId: 'username-input',
localizeAlias: this.usernameIsEmail ? 'auth_email' : 'auth_username',
Expand All @@ -183,9 +281,12 @@
localizeAlias: 'auth_password',
localizeFallback: 'Password',
});

this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput);
this._passwordLayoutItem = createFormLayoutItem(this._passwordLabel, this._passwordInput);
this._passwordLayoutItem = createFormLayoutPasswordItem(
this._passwordLabel,
this._passwordInput,
this._passwordShowPasswordToggleItem
);

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

Expand Down
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Login/src/localization/lang/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ export default {
returnToLogin: 'Tilbage til log ind',
localLoginDisabled: 'Desværre er det ikke muligt at logge ind direkte. Det er blevet deaktiveret af en login-udbyder.',
friendlyGreeting: 'Hej!',
showPassword: 'Vis adgangskode',
hidePassword: 'Skjul adgangskode',
},
} satisfies UmbLocalizationDictionary;
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Login/src/localization/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,7 @@ export default {
returnToLogin: 'Return to login',
localLoginDisabled: 'Unfortunately, direct login is not possible. It has been disabled by a provider.',
friendlyGreeting: 'Hello',
showPassword: 'Show password',
hidePassword: 'Hide password',
},
} satisfies UmbLocalizationDictionary;