Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
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;
}
124 changes: 117 additions & 7 deletions src/Umbraco.Web.UI.Login/src/auth.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,88 @@
return label;
};

const createShowPasswordToggleButton = (opts: {
id: string;
name: string;
ariaLabelShowPassword: string;
ariaLabelHidePassword: string;
}) => {
const openEyeSVG = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
`;
const closedEyeSVG = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"></path>
<path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"></path>
<path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"></path>
<line x1="2" x2="22" y1="2" y2="22"></line>
</svg>
`;

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 +186,8 @@
_passwordInput?: HTMLInputElement;
_usernameLabel?: HTMLLabelElement;
_passwordLabel?: HTMLLabelElement;
_passwordShowPasswordToggleItem?: HTMLSpanElement;
_passwordShowPasswordToggleButton?: HTMLButtonElement;

#authContext = new UmbAuthContext(this, UMB_AUTH_CONTEXT);

Expand All @@ -133,11 +209,33 @@
// 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) => {
// Check if localization is already available
if (this.localize.term('auth_showPassword') !== 'auth_showPassword') {
resolve();
return;
}

let retryCount = 0;
// Retries 40 times with a 50ms interval = 2 seconds
const maxRetries = 40;

// If not, we check periodically until it is available or we reach the max retries
const checkInterval = setInterval(() => {
if (this.localize.term('auth_showPassword') !== 'auth_showPassword' || retryCount >= maxRetries) {
clearInterval(checkInterval);
resolve();
}
retryCount++;
}, 50);
});
}

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

Check warning on line 250 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 +273,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 +290,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._passwordShowPassswordToggleItem
);

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;
Loading